diff --git a/CHANGELOG.md b/CHANGELOG.md index ef841ec0337f2..078b93bd7199c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1977,7 +1977,7 @@ Tests: * [#686](https://github.com/magento/magento2/issues/686) -- Product save validation errors in the admin don't hide the overlay * [#702](https://github.com/magento/magento2/issues/702) -- Base table or view not found * [#652](https://github.com/magento/magento2/issues/652) -- Multishipping checkout not to change the Billing address js issue - * [#648](https://github.com/magento/magento2/issues/648) -- An equal (=) sign in the hash of the product page to to break the tabs functionality + * [#648](https://github.com/magento/magento2/issues/648) -- An equal (=) sign in the hash of the product page to break the tabs functionality * Service Contracts: * Refactored usage of new API of the Customer module * Implemented Service Contracts for the Sales module diff --git a/README.md b/README.md index a2cf536bb6520..6b2ac458eb403 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ To learn more about issue gate labels click [here](https://github.com/magento/ma

Reporting security issues

-To report security vulnerabilities in Magento software or web sites, please e-mail security@magento.com. Please do not report security issues using GitHub. Be sure to encrypt your e-mail with our encryption key if it includes sensitive information. Learn more about reporting security issues here. +To report security vulnerabilities in Magento software or web sites, please create a Bugcrowd researcher account there to submit and follow-up your issue. Learn more about reporting security issues here. Stay up-to-date on the latest security news and patches for Magento by signing up for Security Alert Notifications. diff --git a/app/bootstrap.php b/app/bootstrap.php index ba62b296bd49c..70b632537a75b 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -54,8 +54,16 @@ && isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false ) { - \Magento\Framework\Profiler::applyConfig( - (isset($_SERVER['MAGE_PROFILER']) && strlen($_SERVER['MAGE_PROFILER'])) ? $_SERVER['MAGE_PROFILER'] : trim(file_get_contents(BP . '/var/profiler.flag')), + $profilerConfig = isset($_SERVER['MAGE_PROFILER']) && strlen($_SERVER['MAGE_PROFILER']) + ? $_SERVER['MAGE_PROFILER'] + : trim(file_get_contents(BP . '/var/profiler.flag')); + + if ($profilerConfig) { + $profilerConfig = json_decode($profilerConfig, true) ?: $profilerConfig; + } + + Magento\Framework\Profiler::applyConfig( + $profilerConfig, BP, !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' ); diff --git a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php index 3275de2a82fb7..24ef712c0f61f 100644 --- a/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php +++ b/app/code/Magento/AdminNotification/Observer/PredispatchAdminActionControllerObserver.php @@ -37,7 +37,7 @@ public function __construct( } /** - * Predispath admin action controller + * Predispatch admin action controller * * @param \Magento\Framework\Event\Observer $observer * @return void diff --git a/app/code/Magento/AdminNotification/Test/Mftf/composer.json b/app/code/Magento/AdminNotification/Test/Mftf/composer.json deleted file mode 100644 index 6dcd053378c1b..0000000000000 --- a/app/code/Magento/AdminNotification/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-admin-notification", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml index fbed5c0960b73..04d700b9f90ce 100644 --- a/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml +++ b/app/code/Magento/AdminNotification/etc/adminhtml/menu.xml @@ -7,6 +7,6 @@ --> - + diff --git a/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json b/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json index df5e14e066636..b068ffffe9219 100644 --- a/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json +++ b/app/code/Magento/AdminNotification/etc/db_schema_whitelist.json @@ -1,32 +1,32 @@ { - "adminnotification_inbox": { - "column": { - "notification_id": true, - "severity": true, - "date_added": true, - "title": true, - "description": true, - "url": true, - "is_read": true, - "is_remove": true + "adminnotification_inbox": { + "column": { + "notification_id": true, + "severity": true, + "date_added": true, + "title": true, + "description": true, + "url": true, + "is_read": true, + "is_remove": true + }, + "index": { + "ADMINNOTIFICATION_INBOX_SEVERITY": true, + "ADMINNOTIFICATION_INBOX_IS_READ": true, + "ADMINNOTIFICATION_INBOX_IS_REMOVE": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "ADMINNOTIFICATION_INBOX_SEVERITY": true, - "ADMINNOTIFICATION_INBOX_IS_READ": true, - "ADMINNOTIFICATION_INBOX_IS_REMOVE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "admin_system_messages": { - "column": { - "identity": true, - "severity": true, - "created_at": true - }, - "constraint": { - "PRIMARY": true + "admin_system_messages": { + "column": { + "identity": true, + "severity": true, + "created_at": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml index d654504a41e5c..0448daaf17644 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml +++ b/app/code/Magento/AdminNotification/view/adminhtml/templates/system/messages/popup.phtml @@ -23,7 +23,7 @@ { "[data-role=system_messages_list]": { "Magento_AdminNotification/js/system/messages/popup": { - class: 'modal-system-messages ui-popup-message' + "class":"modal-system-messages ui-popup-message" } } } diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js index f3f6a5fb1a123..39c61d6e07d29 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js +++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/system/messages/popup.js @@ -1,24 +1,26 @@ /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. -*/ + */ define([ 'jquery', 'Magento_Ui/js/modal/modal' -], function ($) { +], function ($, modal) { 'use strict'; return function (data, element) { - if (this.modal) { - this.modal.html($(element).html()); + + if (modal.modal) { + modal.modal.html($(element).html()); } else { - this.modal = $(element).modal({ + modal.modal = $(element).modal({ modalClass: data.class, type: 'popup', buttons: [] }); } - this.modal.modal('openModal'); + + modal.modal.modal('openModal'); }; }); diff --git a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php index 02413a1899cd7..818bcda1da65f 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -37,10 +37,10 @@ public function execute() ); return $resultLayout; } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } else { - $this->messageManager->addError(__('Please correct the data sent.')); + $this->messageManager->addErrorMessage(__('Please correct the data sent.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/composer.json b/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/composer.json deleted file mode 100644 index df2817ad6a52f..0000000000000 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-advanced-pricing-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php index 6b266c76c32f3..57ceb7f5af275 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Export/AdvancedPricingTest.php @@ -219,7 +219,7 @@ protected function setUp() '_getCustomerGroupById', 'correctExportData' ]); - $this->advancedPricing = $this->getMockbuilder( + $this->advancedPricing = $this->getMockBuilder( \Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing::class ) ->setMethods($mockMethods) diff --git a/app/code/Magento/AdvancedSearch/Block/SearchData.php b/app/code/Magento/AdvancedSearch/Block/SearchData.php index 993731b465257..105a1c1c4fc46 100644 --- a/app/code/Magento/AdvancedSearch/Block/SearchData.php +++ b/app/code/Magento/AdvancedSearch/Block/SearchData.php @@ -30,7 +30,7 @@ abstract class SearchData extends Template implements SearchDataInterface /** * @var string */ - protected $_template = 'search_data.phtml'; + protected $_template = 'Magento_AdvancedSearch::search_data.phtml'; /** * @param Template\Context $context diff --git a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php index c2379e9dff062..b20872da2f8e7 100644 --- a/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php +++ b/app/code/Magento/AdvancedSearch/Model/ResourceModel/Index.php @@ -6,18 +6,23 @@ namespace Magento\AdvancedSearch\Model\ResourceModel; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; use Magento\Framework\Search\Request\Dimension; use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; +use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; /** * @api * @since 100.1.0 + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Index extends AbstractDb { @@ -38,25 +43,39 @@ class Index extends AbstractDb */ private $tableResolver; + /** + * @var DimensionCollectionFactory|null + */ + private $dimensionCollectionFactory; + + /** + * @var int|null + */ + private $websiteId; + /** * Index constructor. * @param Context $context * @param StoreManagerInterface $storeManager * @param MetadataPool $metadataPool - * @param null $connectionName + * @param string|null $connectionName * @param TableResolver|null $tableResolver + * @param DimensionCollectionFactory|null $dimensionCollectionFactory */ public function __construct( Context $context, StoreManagerInterface $storeManager, MetadataPool $metadataPool, $connectionName = null, - TableResolver $tableResolver = null + TableResolver $tableResolver = null, + DimensionCollectionFactory $dimensionCollectionFactory = null ) { parent::__construct($context, $connectionName); $this->storeManager = $storeManager; $this->metadataPool = $metadataPool; - $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); + $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(IndexScopeResolverInterface::class); + $this->dimensionCollectionFactory = $dimensionCollectionFactory + ?: ObjectManager::getInstance()->get(DimensionCollectionFactory::class); } /** @@ -78,18 +97,27 @@ protected function _construct() protected function _getCatalogProductPriceData($productIds = null) { $connection = $this->getConnection(); - - $select = $connection->select()->from( - $this->getTable('catalog_product_index_price'), - ['entity_id', 'customer_group_id', 'website_id', 'min_price'] - ); - - if ($productIds) { - $select->where('entity_id IN (?)', $productIds); + $catalogProductIndexPriceSelect = []; + + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + if (!isset($dimensions[WebsiteDimensionProvider::DIMENSION_NAME]) || + $this->websiteId === null || + $dimensions[WebsiteDimensionProvider::DIMENSION_NAME]->getValue() === $this->websiteId) { + $select = $connection->select()->from( + $this->tableResolver->resolve('catalog_product_index_price', $dimensions), + ['entity_id', 'customer_group_id', 'website_id', 'min_price'] + ); + if ($productIds) { + $select->where('entity_id IN (?)', $productIds); + } + $catalogProductIndexPriceSelect[] = $select; + } } + $catalogProductIndexPriceUnionSelect = $connection->select()->union($catalogProductIndexPriceSelect); + $result = []; - foreach ($connection->fetchAll($select) as $row) { + foreach ($connection->fetchAll($catalogProductIndexPriceUnionSelect) as $row) { $result[$row['website_id']][$row['entity_id']][$row['customer_group_id']] = round($row['min_price'], 2); } @@ -106,9 +134,12 @@ protected function _getCatalogProductPriceData($productIds = null) */ public function getPriceIndexData($productIds, $storeId) { + $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); + + $this->websiteId = $websiteId; $priceProductsIndexData = $this->_getCatalogProductPriceData($productIds); + $this->websiteId = null; - $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); if (!isset($priceProductsIndexData[$websiteId])) { return []; } diff --git a/app/code/Magento/AdvancedSearch/Test/Mftf/composer.json b/app/code/Magento/AdvancedSearch/Test/Mftf/composer.json deleted file mode 100644 index b5582c72a039a..0000000000000 --- a/app/code/Magento/AdvancedSearch/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-advanced-search", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-search": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-search": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php index 185e932406e5b..1f37e40842f54 100644 --- a/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/AdvancedSearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -15,6 +15,9 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Select; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class IndexTest extends \PHPUnit\Framework\TestCase { /** @@ -59,10 +62,24 @@ protected function setUp() $this->resourceConnectionMock->expects($this->any())->method('getConnection')->willReturn($this->adapterMock); $this->metadataPoolMock = $this->createMock(MetadataPool::class); + $indexScopeResolverMock = $this->createMock( + \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver::class + ); + $traversableMock = $this->createMock(\Traversable::class); + $dimensionsMock = $this->createMock(\Magento\Framework\Indexer\MultiDimensionProvider::class); + $dimensionsMock->method('getIterator')->willReturn($traversableMock); + $dimensionFactoryMock = $this->createMock( + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + ); + $dimensionFactoryMock->method('create')->willReturn($dimensionsMock); + $this->model = new Index( $this->resourceContextMock, $this->storeManagerMock, - $this->metadataPoolMock + $this->metadataPoolMock, + 'connectionName', + $indexScopeResolverMock, + $dimensionFactoryMock ); } @@ -71,11 +88,13 @@ public function testGetPriceIndexDataUsesFrontendPriceIndexerTable() $storeId = 1; $storeMock = $this->createMock(StoreInterface::class); $storeMock->expects($this->any())->method('getId')->willReturn($storeId); + $storeMock->method('getWebsiteId')->willReturn(1); $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); $selectMock = $this->createMock(Select::class); $selectMock->expects($this->any())->method('from')->willReturnSelf(); $selectMock->expects($this->any())->method('where')->willReturnSelf(); + $selectMock->expects($this->any())->method('union')->willReturnSelf(); $this->adapterMock->expects($this->once())->method('select')->willReturn($selectMock); $this->adapterMock->expects($this->once())->method('fetchAll')->with($selectMock)->willReturn([]); diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json index a224a1001cd01..c6a5af07e60a7 100644 --- a/app/code/Magento/AdvancedSearch/composer.json +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -17,7 +17,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json b/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json index eaf7f3d616736..8addf187744fd 100644 --- a/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json +++ b/app/code/Magento/AdvancedSearch/etc/db_schema_whitelist.json @@ -1,14 +1,14 @@ { - "catalogsearch_recommendations": { - "column": { - "id": true, - "query_id": true, - "relation_id": true - }, - "constraint": { - "PRIMARY": true, - "CATALOGSEARCH_RECOMMENDATIONS_QUERY_ID_SEARCH_QUERY_QUERY_ID": true, - "CATALOGSEARCH_RECOMMENDATIONS_RELATION_ID_SEARCH_QUERY_QUERY_ID": true + "catalogsearch_recommendations": { + "column": { + "id": true, + "query_id": true, + "relation_id": true + }, + "constraint": { + "PRIMARY": true, + "CATALOGSEARCH_RECOMMENDATIONS_QUERY_ID_SEARCH_QUERY_QUERY_ID": true, + "CATALOGSEARCH_RECOMMENDATIONS_RELATION_ID_SEARCH_QUERY_QUERY_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/AdvancedSearch/etc/di.xml b/app/code/Magento/AdvancedSearch/etc/di.xml index 9ec75f56bbf7b..21e19fd58825b 100644 --- a/app/code/Magento/AdvancedSearch/etc/di.xml +++ b/app/code/Magento/AdvancedSearch/etc/di.xml @@ -19,6 +19,12 @@ Did you mean + + + Magento\CatalogSearch\Model\ResourceModel\EngineInterface::CONFIG_ENGINE_PATH + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + + @@ -26,5 +32,12 @@ + + + + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + + + diff --git a/app/code/Magento/Amqp/Test/Mftf/composer.json b/app/code/Magento/Amqp/Test/Mftf/composer.json deleted file mode 100644 index 348200c5d1cb4..0000000000000 --- a/app/code/Magento/Amqp/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-amqp", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/framework-amqp": "100.0.0-dev", - "magento/framework-message-queue": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json index 23130dfb01a4e..b50e951b46f81 100644 --- a/app/code/Magento/Amqp/composer.json +++ b/app/code/Magento/Amqp/composer.json @@ -12,7 +12,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php index 474398fd34e26..ddd9fcba21109 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/ConverterInterface.php @@ -30,4 +30,9 @@ public function toBody(array $data); * @return string */ public function getContentTypeHeader(); + + /** + * @return string + */ + public function getContentMediaType(): string; } diff --git a/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php index 44c54f67da759..059dab554bd92 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/JsonConverter.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Model\Connector\Http; use Magento\Framework\Serialize\Serializer\Json; @@ -14,9 +16,16 @@ class JsonConverter implements ConverterInterface { /** * Content-Type HTTP header for json. + * @deprecated + * @see CONTENT_MEDIA_TYPE */ const CONTENT_TYPE_HEADER = 'Content-Type: application/json'; + /** + * Media-Type corresponding to this converter. + */ + const CONTENT_MEDIA_TYPE = 'application/json'; + /** * @var Json */ @@ -56,6 +65,14 @@ public function toBody(array $data) */ public function getContentTypeHeader() { - return self::CONTENT_TYPE_HEADER; + return sprintf('Content-Type: %s', self::CONTENT_MEDIA_TYPE); + } + + /** + * @inheritdoc + */ + public function getContentMediaType(): string + { + return self::CONTENT_MEDIA_TYPE; } } diff --git a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php index ec198e4a3c40b..57b61c1b5562a 100644 --- a/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php +++ b/app/code/Magento/Analytics/Model/Connector/Http/ResponseResolver.php @@ -38,7 +38,15 @@ public function __construct(ConverterInterface $converter, array $responseHandle public function getResult(\Zend_Http_Response $response) { $result = false; - $responseBody = $this->converter->fromBody($response->getBody()); + $converterMediaType = $this->converter->getContentMediaType(); + + /** Content-Type header may not only contain media-type declaration */ + if ($response->getBody() && is_int(strripos($response->getHeader('Content-Type'), $converterMediaType))) { + $responseBody = $this->converter->fromBody($response->getBody()); + } else { + $responseBody = []; + } + if (array_key_exists($response->getStatus(), $this->responseHandlers)) { $result = $this->responseHandlers[$response->getStatus()]->handleResponse($responseBody); } diff --git a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php index dfa283e10d070..c05357400d075 100644 --- a/app/code/Magento/Analytics/Model/Connector/OTPRequest.php +++ b/app/code/Magento/Analytics/Model/Connector/OTPRequest.php @@ -103,8 +103,9 @@ public function call() if (!$result) { $this->logger->warning( sprintf( - 'Obtaining of an OTP from the MBI service has been failed: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Obtaining of an OTP from the MBI service has been failed: %s. Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php index c1f8152f3134e..e35c9bb42bc43 100644 --- a/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php +++ b/app/code/Magento/Analytics/Model/Connector/SignUpCommand.php @@ -110,8 +110,10 @@ public function execute() if (!$result) { $this->logger->warning( sprintf( - 'Subscription for MBI service has been failed. An error occurred during token exchange: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Subscription for MBI service has been failed. An error occurred during token exchange: %s.' + . ' Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php index 7b4e452a7b451..59878ff9c0814 100644 --- a/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php +++ b/app/code/Magento/Analytics/Model/Connector/UpdateCommand.php @@ -101,8 +101,9 @@ public function execute() if (!$result) { $this->logger->warning( sprintf( - 'Update of the subscription for MBI service has been failed: %s', - !empty($response->getBody()) ? $response->getBody() : 'Response body is empty.' + 'Update of the subscription for MBI service has been failed: %s. Content-Type: %s', + !empty($response->getBody()) ? $response->getBody() : 'Response body is empty', + $response->getHeader('Content-Type') ) ); } diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml index 8324ad5ba995a..f6e5b955816e2 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> noreport No diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml index 71d8bdcd5994b..099cc71321b84 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> noreport 123123q diff --git a/app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml b/app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml index 06186d2d10402..2e1e5f6f5a97d 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Metadata/user-meta.xml @@ -6,7 +6,7 @@ */ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> application/x-www-form-urlencoded diff --git a/app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml b/app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml index f52468807928e..9d0132453c798 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Metadata/user_role-meta.xml @@ -6,7 +6,7 @@ */ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> application/x-www-form-urlencoded diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml index 8649eac484d94..ff89ca9b663ee 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml @@ -6,7 +6,7 @@ */ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> @@ -22,6 +22,7 @@ + diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml index 8b0714cd2ef65..d9617209dcdff 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -6,7 +6,7 @@ */ --> - + @@ -16,8 +16,9 @@ - - + + + diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml index 6b38bccf33dd5..2d5594a43b1a7 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> @@ -17,8 +17,9 @@ - - + + + diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml index 501dba3dba23b..d8aed1250d82a 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> @@ -17,8 +17,9 @@ - - + + + diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml index fc1ff7d18b51e..3f17df108b50b 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> diff --git a/app/code/Magento/Analytics/Test/Mftf/composer.json b/app/code/Magento/Analytics/Test/Mftf/composer.json deleted file mode 100644 index cc288fbbb130d..0000000000000 --- a/app/code/Magento/Analytics/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php index cbf06264096ac..407e323aeaae6 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/AdditionalCommentTest.php @@ -65,11 +65,11 @@ public function testRender() ->method('getLabel') ->willReturn('Comment label'); $html = $this->additionalComment->render($this->abstractElementMock); - $this->assertRegexp( + $this->assertRegExp( "/New comment/", $html ); - $this->assertRegexp( + $this->assertRegExp( "/Comment label/", $html ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php index 462b3c909a7fd..d567d65882350 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php @@ -87,7 +87,7 @@ public function testRender() $this->localeResolver->expects($this->once()) ->method('getLocale') ->willReturn('en_US'); - $this->assertRegexp( + $this->assertRegExp( "/Eastern Standard Time \(America\/New_York\)/", $this->collectionTimeLabel->render($this->abstractElementMock) ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php index d643bc05cc615..78ff581f3de9d 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/SubscriptionStatusLabelTest.php @@ -74,7 +74,7 @@ public function testRender() $this->abstractElementMock->expects($this->any()) ->method('getComment') ->willReturn('Subscription status: Enabled'); - $this->assertRegexp( + $this->assertRegExp( "/Subscription status: Enabled/", $this->subscriptionStatusLabel->render($this->abstractElementMock) ); diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php index abce48c36c86a..6a0cecc781062 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/VerticalTest.php @@ -65,7 +65,7 @@ public function testRender() ->method('getHint') ->willReturn('New hint'); $html = $this->vertical->render($this->abstractElementMock); - $this->assertRegexp( + $this->assertRegExp( "/New comment/", $html ); diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php index 5ee59a7913a61..92f79c2bf6dee 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/Client/CurlTest.php @@ -12,6 +12,7 @@ /** * A unit test for testing of the CURL HTTP client. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CurlTest extends \PHPUnit\Framework\TestCase { @@ -97,7 +98,6 @@ public function getTestData() 'version' => '1.1', 'body'=> ['name' => 'value'], 'url' => 'http://www.mystore.com', - 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], 'method' => \Magento\Framework\HTTP\ZendClient::POST, ] ] @@ -105,7 +105,9 @@ public function getTestData() } /** + * @param array $data * @return void + * @throws \Zend_Http_Exception * @dataProvider getTestData */ public function testRequestSuccess(array $data) @@ -118,7 +120,7 @@ public function testRequestSuccess(array $data) $data['method'], $data['url'], $data['version'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], json_encode($data['body']) ); $this->curlAdapterMock->expects($this->once()) @@ -139,14 +141,16 @@ public function testRequestSuccess(array $data) $data['method'], $data['url'], $data['body'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], $data['version'] ) ); } /** + * @param array $data * @return void + * @throws \Zend_Http_Exception * @dataProvider getTestData */ public function testRequestError(array $data) @@ -158,7 +162,7 @@ public function testRequestError(array $data) $data['method'], $data['url'], $data['version'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], json_encode($data['body']) ); $this->curlAdapterMock->expects($this->once()) @@ -184,7 +188,7 @@ public function testRequestError(array $data) $data['method'], $data['url'], $data['body'], - $data['headers'], + [$this->converterMock->getContentTypeHeader()], $data['version'] ) ); @@ -195,14 +199,13 @@ public function testRequestError(array $data) */ private function createJsonConverter() { - $converterMock = $this->getMockBuilder(ConverterInterface::class) - ->getMockForAbstractClass(); + $converterMock = $this->getMockBuilder(JsonConverter::class) + ->setMethodsExcept(['getContentTypeHeader']) + ->disableOriginalConstructor() + ->getMock(); $converterMock->expects($this->any())->method('toBody')->willReturnCallback(function ($value) { return json_encode($value); }); - $converterMock->expects($this->any()) - ->method('getContentTypeHeader') - ->willReturn(JsonConverter::CONTENT_TYPE_HEADER); return $converterMock; } } diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php index 251f0d1474083..d3258c8ae9caa 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/JsonConverterTest.php @@ -25,6 +25,9 @@ class JsonConverterTest extends \PHPUnit\Framework\TestCase */ private $converter; + /** + * @return void + */ protected function setUp() { $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -37,9 +40,15 @@ protected function setUp() ); } + /** + * @return void + */ public function testConverterContainsHeader() { - $this->assertEquals(JsonConverter::CONTENT_TYPE_HEADER, $this->converter->getContentTypeHeader()); + $this->assertEquals( + 'Content-Type: ' . JsonConverter::CONTENT_MEDIA_TYPE, + $this->converter->getContentTypeHeader() + ); } /** @@ -66,6 +75,9 @@ public function convertBodyDataProvider() ]; } + /** + * return void + */ public function testConvertData() { $this->serializerMock->expects($this->once()) diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php index 3d4c90bcd07f7..2564240c4fa11 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/Http/ResponseResolverTest.php @@ -3,49 +3,115 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Test\Unit\Model\Connector\Http; -use Magento\Analytics\Model\Connector\Http\JsonConverter; +use Magento\Analytics\Model\Connector\Http\ConverterInterface; use Magento\Analytics\Model\Connector\Http\ResponseHandlerInterface; use Magento\Analytics\Model\Connector\Http\ResponseResolver; -use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; class ResponseResolverTest extends \PHPUnit\Framework\TestCase { - public function testGetResultHandleResponseSuccess() + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var ConverterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $converterMock; + + /** + * @var ResponseHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $successResponseHandlerMock; + + /** + * @var ResponseHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $notFoundResponseHandlerMock; + + /** + * @var ResponseResolver + */ + private $responseResolver; + + /** + * @return void + */ + protected function setUp() { - $expectedBody = ['test' => 'testValue']; - $response = new \Zend_Http_Response(201, [], json_encode($expectedBody)); - $responseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) - ->getMockForAbstractClass(); - $responseHandlerMock->expects($this->once()) - ->method('handleResponse') - ->with($expectedBody) - ->willReturn(true); - $notFoundResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) - ->getMockForAbstractClass(); - $notFoundResponseHandlerMock->expects($this->never())->method('handleResponse'); - $serializerMock = $this->getMockBuilder(Json::class) + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->converterMock = $this->getMockBuilder(ConverterInterface::class) ->disableOriginalConstructor() ->getMock(); - $serializerMock->expects($this->once()) - ->method('unserialize') - ->willReturn($expectedBody); - $objectManager = new ObjectManager($this); - $responseResolver = $objectManager->getObject( + $this->successResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $this->notFoundResponseHandlerMock = $this->getMockBuilder(ResponseHandlerInterface::class) + ->getMockForAbstractClass(); + $this->responseResolver = $this->objectManagerHelper->getObject( ResponseResolver::class, [ - 'converter' => $objectManager->getObject( - JsonConverter::class, - ['serializer' => $serializerMock] - ), + 'converter' => $this->converterMock, 'responseHandlers' => [ - 201 => $responseHandlerMock, - 404 => $notFoundResponseHandlerMock, + 201 => $this->successResponseHandlerMock, + 404 => $this->notFoundResponseHandlerMock, ] ] ); - $this->assertTrue($responseResolver->getResult($response)); + } + + /** + * @return void + * @throws \Zend_Http_Exception + */ + public function testGetResultHandleResponseSuccess() + { + $expectedBody = ['test' => 'testValue']; + $response = new \Zend_Http_Response(201, ['Content-Type' => 'application/json'], json_encode($expectedBody)); + $this->converterMock + ->method('getContentMediaType') + ->willReturn('application/json'); + + $this->successResponseHandlerMock + ->expects($this->once()) + ->method('handleResponse') + ->with($expectedBody) + ->willReturn(true); + $this->notFoundResponseHandlerMock + ->expects($this->never()) + ->method('handleResponse'); + $this->converterMock + ->method('fromBody') + ->willReturn($expectedBody); + $this->assertTrue($this->responseResolver->getResult($response)); + } + + /** + * @return void + * @throws \Zend_Http_Exception + */ + public function testGetResultHandleResponseUnexpectedContentType() + { + $expectedBody = 'testString'; + $response = new \Zend_Http_Response(201, ['Content-Type' => 'plain/text'], $expectedBody); + $this->converterMock + ->method('getContentMediaType') + ->willReturn('application/json'); + $this->converterMock + ->expects($this->never()) + ->method('fromBody'); + $this->successResponseHandlerMock + ->expects($this->once()) + ->method('handleResponse') + ->with([]) + ->willReturn(false); + $this->notFoundResponseHandlerMock + ->expects($this->never()) + ->method('handleResponse'); + $this->assertFalse($this->responseResolver->getResult($response)); } } diff --git a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php index db6cda7153c1a..c113b2dc275dd 100644 --- a/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Model/Connector/SignUpCommandTest.php @@ -57,6 +57,9 @@ class SignUpCommandTest extends \PHPUnit\Framework\TestCase */ private $responseResolverMock; + /** + * @return void + */ protected function setUp() { $this->analyticsTokenMock = $this->getMockBuilder(AnalyticsToken::class) @@ -91,6 +94,10 @@ protected function setUp() ); } + /** + * @throws \Zend_Http_Exception + * @return void + */ public function testExecuteSuccess() { $this->integrationManagerMock->expects($this->once()) @@ -124,6 +131,9 @@ public function testExecuteSuccess() $this->assertTrue($this->signUpCommand->execute()); } + /** + * @return void + */ public function testExecuteFailureCannotGenerateToken() { $this->integrationManagerMock->expects($this->once()) @@ -134,6 +144,10 @@ public function testExecuteFailureCannotGenerateToken() $this->assertFalse($this->signUpCommand->execute()); } + /** + * @throws \Zend_Http_Exception + * @return void + */ public function testExecuteFailureResponseIsEmpty() { $this->integrationManagerMock->expects($this->once()) @@ -163,7 +177,6 @@ private function getTestData() 'url' => 'http://www.mystore.com', 'access-token' => 'thisisaccesstoken', 'integration-token' => 'thisisintegrationtoken', - 'headers' => [JsonConverter::CONTENT_TYPE_HEADER], 'method' => \Magento\Framework\HTTP\ZendClient::POST, 'body'=> ['token' => 'thisisintegrationtoken','url' => 'http://www.mystore.com'], ]; diff --git a/app/code/Magento/AsynchronousOperations/Test/Mftf/composer.json b/app/code/Magento/AsynchronousOperations/Test/Mftf/composer.json deleted file mode 100644 index baadc0d0d914a..0000000000000 --- a/app/code/Magento/AsynchronousOperations/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-asynchronous-operations", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/framework-bulk": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "suggest": { - "magento/functional-test-module-admin-notification": "100.0.0-dev", - "magento/functional-test-module-logging": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index 3acb92710e62b..7d5a097eeea3f 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -19,7 +19,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json b/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json index 396e443355d8f..d62f16ffca8cd 100644 --- a/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json +++ b/app/code/Magento/AsynchronousOperations/etc/db_schema_whitelist.json @@ -1,47 +1,47 @@ { - "magento_bulk": { - "column": { - "id": true, - "uuid": true, - "user_id": true, - "description": true, - "operation_count": true, - "start_time": true + "magento_bulk": { + "column": { + "id": true, + "uuid": true, + "user_id": true, + "description": true, + "operation_count": true, + "start_time": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_BULK_USER_ID_ADMIN_USER_USER_ID": true, + "MAGENTO_BULK_UUID": true + } }, - "constraint": { - "PRIMARY": true, - "MAGENTO_BULK_USER_ID_ADMIN_USER_USER_ID": true, - "MAGENTO_BULK_UUID": true - } - }, - "magento_operation": { - "column": { - "id": true, - "bulk_uuid": true, - "topic_name": true, - "serialized_data": true, - "result_serialized_data": true, - "status": true, - "error_code": true, - "result_message": true - }, - "index": { - "MAGENTO_OPERATION_BULK_UUID_ERROR_CODE": true - }, - "constraint": { - "PRIMARY": true, - "MAGENTO_OPERATION_BULK_UUID_MAGENTO_BULK_UUID": true - } - }, - "magento_acknowledged_bulk": { - "column": { - "id": true, - "bulk_uuid": true + "magento_operation": { + "column": { + "id": true, + "bulk_uuid": true, + "topic_name": true, + "serialized_data": true, + "result_serialized_data": true, + "status": true, + "error_code": true, + "result_message": true + }, + "index": { + "MAGENTO_OPERATION_BULK_UUID_ERROR_CODE": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_OPERATION_BULK_UUID_MAGENTO_BULK_UUID": true + } }, - "constraint": { - "PRIMARY": true, - "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID_MAGENTO_BULK_UUID": true, - "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID": true + "magento_acknowledged_bulk": { + "column": { + "id": true, + "bulk_uuid": true + }, + "constraint": { + "PRIMARY": true, + "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID_MAGENTO_BULK_UUID": true, + "MAGENTO_ACKNOWLEDGED_BULK_BULK_UUID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Authorization/Test/Mftf/composer.json b/app/code/Magento/Authorization/Test/Mftf/composer.json deleted file mode 100644 index fddc5acb034ed..0000000000000 --- a/app/code/Magento/Authorization/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-authorization", - "description": "Authorization module provides access to Magento ACL functionality.", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Authorization/etc/db_schema_whitelist.json b/app/code/Magento/Authorization/etc/db_schema_whitelist.json index eb9256e415593..8c416d2a8b42c 100644 --- a/app/code/Magento/Authorization/etc/db_schema_whitelist.json +++ b/app/code/Magento/Authorization/etc/db_schema_whitelist.json @@ -1,38 +1,38 @@ { - "authorization_role": { - "column": { - "role_id": true, - "parent_id": true, - "tree_level": true, - "sort_order": true, - "role_type": true, - "user_id": true, - "user_type": true, - "role_name": true + "authorization_role": { + "column": { + "role_id": true, + "parent_id": true, + "tree_level": true, + "sort_order": true, + "role_type": true, + "user_id": true, + "user_type": true, + "role_name": true + }, + "index": { + "AUTHORIZATION_ROLE_PARENT_ID_SORT_ORDER": true, + "AUTHORIZATION_ROLE_TREE_LEVEL": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "AUTHORIZATION_ROLE_PARENT_ID_SORT_ORDER": true, - "AUTHORIZATION_ROLE_TREE_LEVEL": true - }, - "constraint": { - "PRIMARY": true - } - }, - "authorization_rule": { - "column": { - "rule_id": true, - "role_id": true, - "resource_id": true, - "privileges": true, - "permission": true - }, - "index": { - "AUTHORIZATION_RULE_RESOURCE_ID_ROLE_ID": true, - "AUTHORIZATION_RULE_ROLE_ID_RESOURCE_ID": true - }, - "constraint": { - "PRIMARY": true, - "AUTHORIZATION_RULE_ROLE_ID_AUTHORIZATION_ROLE_ROLE_ID": true + "authorization_rule": { + "column": { + "rule_id": true, + "role_id": true, + "resource_id": true, + "privileges": true, + "permission": true + }, + "index": { + "AUTHORIZATION_RULE_RESOURCE_ID_ROLE_ID": true, + "AUTHORIZATION_RULE_ROLE_ID_RESOURCE_ID": true + }, + "constraint": { + "PRIMARY": true, + "AUTHORIZATION_RULE_ROLE_ID_AUTHORIZATION_ROLE_ROLE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php index 3ad9f470909bf..70565ea8ac65f 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponse.php @@ -10,12 +10,15 @@ use Magento\Authorizenet\Model\Directpost; use Magento\Authorizenet\Model\DirectpostFactory; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; use Psr\Log\LoggerInterface; -class BackendResponse extends \Magento\Authorizenet\Controller\Directpost\Payment +class BackendResponse extends \Magento\Authorizenet\Controller\Directpost\Payment implements CsrfAwareActionInterface { /** * @var LoggerInterface @@ -48,6 +51,23 @@ public function __construct( $this->logger = $logger ?: $this->_objectManager->get(LoggerInterface::class); } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Response action. * Action for Authorize.net SIM Relay Request. diff --git a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php index d88e77d6c4e28..d562df9fb24a9 100644 --- a/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php +++ b/app/code/Magento/Authorizenet/Controller/Directpost/Payment/Response.php @@ -6,8 +6,29 @@ */ namespace Magento\Authorizenet\Controller\Directpost\Payment; -class Response extends \Magento\Authorizenet\Controller\Directpost\Payment +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +class Response extends \Magento\Authorizenet\Controller\Directpost\Payment implements CsrfAwareActionInterface { + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Response action. * Action for Authorize.net SIM Relay Request. diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index 5476fd05a0fed..d5c11ab54cd94 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -814,10 +814,14 @@ protected function declineOrder(\Magento\Sales\Model\Order $order, $message = '' { try { $response = $this->getResponse(); - if ($voidPayment && $response->getXTransId() && strtoupper($response->getXType()) - == self::REQUEST_TYPE_AUTH_ONLY + if ($voidPayment + && $response->getXTransId() + && strtoupper($response->getXType()) == self::REQUEST_TYPE_AUTH_ONLY ) { - $order->getPayment()->setTransactionId(null)->setParentTransactionId($response->getXTransId())->void(); + $order->getPayment() + ->setTransactionId(null) + ->setParentTransactionId($response->getXTransId()) + ->void($response); } $order->registerCancellation($message)->save(); } catch (\Exception $e) { diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request.php b/app/code/Magento/Authorizenet/Model/Directpost/Request.php index d9a403e5c991e..fc78d836b6080 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Request.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Request.php @@ -112,50 +112,50 @@ public function setDataFromOrder( sprintf('%.2F', $order->getBaseShippingAmount()) ); - //need to use strval() because NULL values IE6-8 decodes as "null" in JSON in JavaScript, + //need to use (string) because NULL values IE6-8 decodes as "null" in JSON in JavaScript, //but we need "" for null values. $billing = $order->getBillingAddress(); if (!empty($billing)) { - $this->setXFirstName(strval($billing->getFirstname())) - ->setXLastName(strval($billing->getLastname())) - ->setXCompany(strval($billing->getCompany())) - ->setXAddress(strval($billing->getStreetLine(1))) - ->setXCity(strval($billing->getCity())) - ->setXState(strval($billing->getRegion())) - ->setXZip(strval($billing->getPostcode())) - ->setXCountry(strval($billing->getCountryId())) - ->setXPhone(strval($billing->getTelephone())) - ->setXFax(strval($billing->getFax())) - ->setXCustId(strval($billing->getCustomerId())) - ->setXCustomerIp(strval($order->getRemoteIp())) - ->setXCustomerTaxId(strval($billing->getTaxId())) - ->setXEmail(strval($order->getCustomerEmail())) - ->setXEmailCustomer(strval($paymentMethod->getConfigData('email_customer'))) - ->setXMerchantEmail(strval($paymentMethod->getConfigData('merchant_email'))); + $this->setXFirstName((string)$billing->getFirstname()) + ->setXLastName((string)$billing->getLastname()) + ->setXCompany((string)$billing->getCompany()) + ->setXAddress((string)$billing->getStreetLine(1)) + ->setXCity((string)$billing->getCity()) + ->setXState((string)$billing->getRegion()) + ->setXZip((string)$billing->getPostcode()) + ->setXCountry((string)$billing->getCountryId()) + ->setXPhone((string)$billing->getTelephone()) + ->setXFax((string)$billing->getFax()) + ->setXCustId((string)$billing->getCustomerId()) + ->setXCustomerIp((string)$order->getRemoteIp()) + ->setXCustomerTaxId((string)$billing->getTaxId()) + ->setXEmail((string)$order->getCustomerEmail()) + ->setXEmailCustomer((string)$paymentMethod->getConfigData('email_customer')) + ->setXMerchantEmail((string)$paymentMethod->getConfigData('merchant_email')); } $shipping = $order->getShippingAddress(); if (!empty($shipping)) { $this->setXShipToFirstName( - strval($shipping->getFirstname()) + (string)$shipping->getFirstname() )->setXShipToLastName( - strval($shipping->getLastname()) + (string)$shipping->getLastname() )->setXShipToCompany( - strval($shipping->getCompany()) + (string)$shipping->getCompany() )->setXShipToAddress( - strval($shipping->getStreetLine(1)) + (string)$shipping->getStreetLine(1) )->setXShipToCity( - strval($shipping->getCity()) + (string)$shipping->getCity() )->setXShipToState( - strval($shipping->getRegion()) + (string)$shipping->getRegion() )->setXShipToZip( - strval($shipping->getPostcode()) + (string)$shipping->getPostcode() )->setXShipToCountry( - strval($shipping->getCountryId()) + (string)$shipping->getCountryId() ); } - $this->setXPoNum(strval($payment->getPoNumber())); + $this->setXPoNum((string)$payment->getPoNumber()); return $this; } diff --git a/app/code/Magento/Authorizenet/Test/Mftf/composer.json b/app/code/Magento/Authorizenet/Test/Mftf/composer.json deleted file mode 100644 index b02806a0ae3f3..0000000000000 --- a/app/code/Magento/Authorizenet/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-authorizenet", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Authorizenet/composer.json b/app/code/Magento/Authorizenet/composer.json index 31f2295da4307..4e646004d9a6d 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -20,7 +20,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Backend/App/AbstractAction.php b/app/code/Magento/Backend/App/AbstractAction.php index 3f658ee90bf4e..fb2daa283f111 100644 --- a/app/code/Magento/Backend/App/AbstractAction.php +++ b/app/code/Magento/Backend/App/AbstractAction.php @@ -205,10 +205,6 @@ private function _moveBlockToContainer(\Magento\Framework\View\Element\AbstractB */ public function dispatch(\Magento\Framework\App\RequestInterface $request) { - if (!$this->_processUrlKeys()) { - return parent::dispatch($request); - } - if ($request->isDispatched() && $request->getActionName() !== 'denied' && !$this->_isAllowed()) { $this->_response->setStatusHeader(403, '1.1', 'Forbidden'); if (!$this->_auth->isLoggedIn()) { @@ -252,6 +248,9 @@ protected function _isUrlChecked() * Check url keys. If non valid - redirect * * @return bool + * + * @see \Magento\Backend\App\Request\BackendValidator for default + * request validation. */ public function _processUrlKeys() { diff --git a/app/code/Magento/Backend/App/Action/Plugin/Authentication.php b/app/code/Magento/Backend/App/Action/Plugin/Authentication.php index 68506a521c1cf..4b25e9921e404 100644 --- a/app/code/Magento/Backend/App/Action/Plugin/Authentication.php +++ b/app/code/Magento/Backend/App/Action/Plugin/Authentication.php @@ -160,7 +160,7 @@ protected function _processNotLoggedInUser(\Magento\Framework\App\RequestInterfa } else { $this->_actionFlag->set('', \Magento\Framework\App\ActionInterface::FLAG_NO_DISPATCH, true); $this->_response->setRedirect($this->_url->getCurrentUrl()); - $this->messageManager->addError(__('Invalid Form Key. Please refresh the page.')); + $this->messageManager->addErrorMessage(__('Invalid Form Key. Please refresh the page.')); $isRedirectNeeded = true; } } @@ -205,7 +205,7 @@ protected function _performLogin(\Magento\Framework\App\RequestInterface $reques $this->_auth->login($username, $password); } catch (AuthenticationException $e) { if (!$request->getParam('messageSent')) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $request->setParam('messageSent', true); $outputValue = false; } diff --git a/app/code/Magento/Backend/App/Request/BackendValidator.php b/app/code/Magento/Backend/App/Request/BackendValidator.php new file mode 100644 index 0000000000000..878f9cb4dc4c1 --- /dev/null +++ b/app/code/Magento/Backend/App/Request/BackendValidator.php @@ -0,0 +1,180 @@ +auth = $auth; + $this->formKeyValidator = $formKeyValidator; + $this->backendUrl = $backendUrl; + $this->redirectFactory = $redirectFactory; + $this->rawResultFactory = $rawResultFactory; + } + + /** + * @param RequestInterface $request + * @param ActionInterface $action + * + * @return bool + */ + private function validateRequest( + RequestInterface $request, + ActionInterface $action + ): bool { + /** @var bool|null $valid */ + $valid = null; + + if ($action instanceof CsrfAwareActionInterface) { + $valid = $action->validateForCsrf($request); + } + + if ($valid === null) { + $validFormKey = true; + $validSecretKey = true; + if ($request instanceof HttpRequest && $request->isPost()) { + $validFormKey = $this->formKeyValidator->validate($request); + } elseif ($this->auth->isLoggedIn() + && $this->backendUrl->useSecretKey() + ) { + $secretKeyValue = (string)$request->getParam( + BackendUrl::SECRET_KEY_PARAM_NAME, + null + ); + $secretKey = $this->backendUrl->getSecretKey(); + $validSecretKey = ($secretKeyValue === $secretKey); + } + $valid = $validFormKey && $validSecretKey; + } + + return $valid; + } + + /** + * @param RequestInterface $request + * @param ActionInterface $action + * + * @return InvalidRequestException + */ + private function createException( + RequestInterface $request, + ActionInterface $action + ): InvalidRequestException { + /** @var InvalidRequestException|null $exception */ + $exception = null; + + if ($action instanceof CsrfAwareActionInterface) { + $exception = $action->createCsrfValidationException($request); + } + + if ($exception === null) { + if ($request instanceof HttpRequest && $request->isAjax()) { + //Sending empty response for AJAX request since we don't know + //the expected response format and it's pointless to redirect. + /** @var RawResult $response */ + $response = $this->rawResultFactory->create(); + $response->setHttpResponseCode(401); + $response->setContents(''); + $exception = new InvalidRequestException($response); + } else { + //For regular requests. + $response = $this->redirectFactory->create() + ->setUrl($this->backendUrl->getStartupPageUrl()); + $exception = new InvalidRequestException( + $response, + [ + new Phrase( + 'Invalid security or form key. Please refresh the page.' + ) + ] + ); + } + } + + return $exception; + } + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + if ($action instanceof AbstractAction) { + //Abstract Action has build-in validation. + if (!$action->_processUrlKeys()) { + throw new InvalidRequestException($action->getResponse()); + } + } else { + //Fallback validation. + if (!$this->validateRequest($request, $action)) { + throw $this->createException($request, $action); + } + } + } +} diff --git a/app/code/Magento/Backend/Block/Dashboard/Bar.php b/app/code/Magento/Backend/Block/Dashboard/Bar.php index 29557f12c1093..7ccb2d51ccd1b 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Bar.php +++ b/app/code/Magento/Backend/Block/Dashboard/Bar.php @@ -38,14 +38,6 @@ public function getTotals() */ public function addTotal($label, $value, $isQuantity = false) { - /*if (!$isQuantity) { - $value = $this->format($value); - $decimals = substr($value, -2); - $value = substr($value, 0, -2); - } else { - $value = ($value != '')?$value:0; - $decimals = ''; - }*/ if (!$isQuantity) { $value = $this->format($value); } diff --git a/app/code/Magento/Backend/Block/System/Store/Delete/Group.php b/app/code/Magento/Backend/Block/System/Store/Delete/Group.php index ae80b56066a6d..e95f3bbf9f8c1 100644 --- a/app/code/Magento/Backend/Block/System/Store/Delete/Group.php +++ b/app/code/Magento/Backend/Block/System/Store/Delete/Group.php @@ -19,7 +19,7 @@ protected function _prepareLayout() { $itemId = $this->getRequest()->getParam('group_id'); - $this->setTemplate('system/store/delete_group.phtml'); + $this->setTemplate('Magento_Backend::system/store/delete_group.phtml'); $this->setAction($this->getUrl('adminhtml/*/deleteGroupPost', ['group_id' => $itemId])); $this->addChild( 'confirm_deletion_button', diff --git a/app/code/Magento/Backend/Block/System/Store/Delete/Website.php b/app/code/Magento/Backend/Block/System/Store/Delete/Website.php index da28a471130cc..82cbb780137b8 100644 --- a/app/code/Magento/Backend/Block/System/Store/Delete/Website.php +++ b/app/code/Magento/Backend/Block/System/Store/Delete/Website.php @@ -19,7 +19,7 @@ protected function _prepareLayout() { $itemId = $this->getRequest()->getParam('website_id'); - $this->setTemplate('system/store/delete_website.phtml'); + $this->setTemplate('Magento_Backend::system/store/delete_website.phtml'); $this->setAction($this->getUrl('adminhtml/*/deleteWebsitePost', ['website_id' => $itemId])); $this->addChild( 'confirm_deletion_button', diff --git a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php index 723deab1e9f7e..eff49c3b75ab2 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Element/Dependence.php @@ -124,14 +124,18 @@ protected function _toHtml() if (!$this->_depends) { return ''; } - return ''; + + $params = $this->_getDependsJson(); + + if ($this->_configOptions) { + $params .= ', ' . $this->_jsonEncoder->encode($this->_configOptions); + } + + return ""; } /** diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php index 88d1560026cbd..1d8d658267020 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Column/Filter/Datetime.php @@ -26,7 +26,6 @@ public function getValue($index = null) { if ($index) { if ($data = $this->getData('value', 'orig_' . $index)) { - // date('Y-m-d', strtotime($data)); return $data; } return null; diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php index 3fa60faad45c8..185b1116b8f67 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php @@ -277,13 +277,13 @@ public function getGridIdsJson() } /** @var \Magento\Framework\Data\Collection $allIdsCollection */ $allIdsCollection = clone $this->getParentBlock()->getCollection(); - + if ($this->getMassactionIdField()) { $massActionIdField = $this->getMassactionIdField(); } else { $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } - + $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); if (!empty($gridIds)) { return join(",", $gridIds); diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php index d59d3858179e0..8e0fce2b16cc9 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php @@ -274,13 +274,13 @@ public function getGridIdsJson() /** @var \Magento\Framework\Data\Collection $allIdsCollection */ $allIdsCollection = clone $this->getParentBlock()->getCollection(); - + if ($this->getMassactionIdField()) { $massActionIdField = $this->getMassactionIdField(); } else { $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } - + $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); if (!empty($gridIds)) { diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php index 41e32c929287a..e55c449a0e5bb 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Auth/Logout.php @@ -16,7 +16,7 @@ class Logout extends \Magento\Backend\Controller\Adminhtml\Auth public function execute() { $this->_auth->logout(); - $this->messageManager->addSuccess(__('You have logged out.')); + $this->messageManager->addSuccessMessage(__('You have logged out.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php index 7a926b1c09c3e..888ce11313b8a 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanImages.php @@ -28,11 +28,11 @@ public function execute() try { $this->_objectManager->create(\Magento\Catalog\Model\Product\Image::class)->clearCache(); $this->_eventManager->dispatch('clean_catalog_images_cache_after'); - $this->messageManager->addSuccess(__('The image cache was cleaned.')); + $this->messageManager->addSuccessMessage(__('The image cache was cleaned.')); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while clearing the image cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while clearing the image cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php index 72f23ab65cf8a..5df0a7779c4c1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanMedia.php @@ -28,11 +28,12 @@ public function execute() try { $this->_objectManager->get(\Magento\Framework\View\Asset\MergeService::class)->cleanMergedJsCss(); $this->_eventManager->dispatch('clean_media_cache_after'); - $this->messageManager->addSuccess(__('The JavaScript/CSS cache has been cleaned.')); + $this->messageManager->addSuccessMessage(__('The JavaScript/CSS cache has been cleaned.')); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while clearing the JavaScript/CSS cache.')); + $this->messageManager + ->addExceptionMessage($e, __('An error occurred while clearing the JavaScript/CSS cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php index 27ae2fc31e150..489eb5799a5e7 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/CleanStaticFiles.php @@ -26,7 +26,7 @@ public function execute() { $this->_objectManager->get(\Magento\Framework\App\State\CleanupFiles::class)->clearMaterializedViewFiles(); $this->_eventManager->dispatch('clean_static_files_cache_after'); - $this->messageManager->addSuccess(__('The static files cache has been cleaned.')); + $this->messageManager->addSuccessMessage(__('The static files cache has been cleaned.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php index ca89ea58fa6f3..a2f18b4baf53d 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushAll.php @@ -27,7 +27,7 @@ public function execute() foreach ($this->_cacheFrontendPool as $cacheFrontend) { $cacheFrontend->getBackend()->clean(); } - $this->messageManager->addSuccess(__("You flushed the cache storage.")); + $this->messageManager->addSuccessMessage(__("You flushed the cache storage.")); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php index f0fed159e0f22..90ed3432fa87b 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/FlushSystem.php @@ -27,7 +27,7 @@ public function execute() $cacheFrontend->clean(); } $this->_eventManager->dispatch('adminhtml_cache_flush_system'); - $this->messageManager->addSuccess(__("The Magento cache storage has been flushed.")); + $this->messageManager->addSuccessMessage(__("The Magento cache storage has been flushed.")); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php index 2bfa937b06b77..03b88ca1d3f47 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassDisable.php @@ -67,12 +67,12 @@ private function disableCache() } if ($updatedTypes > 0) { $this->_cacheState->persist(); - $this->messageManager->addSuccess(__("%1 cache type(s) disabled.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) disabled.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while disabling cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while disabling cache.')); } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php index 113e0f2d8961b..1b98a00d4bf35 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassEnable.php @@ -66,12 +66,12 @@ private function enableCache() } if ($updatedTypes > 0) { $this->_cacheState->persist(); - $this->messageManager->addSuccess(__("%1 cache type(s) enabled.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) enabled.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while enabling cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while enabling cache.')); } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php index 3843b030afb3d..bde211debcf72 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Cache/MassRefresh.php @@ -37,12 +37,12 @@ public function execute() $updatedTypes++; } if ($updatedTypes > 0) { - $this->messageManager->addSuccess(__("%1 cache type(s) refreshed.", $updatedTypes)); + $this->messageManager->addSuccessMessage(__("%1 cache type(s) refreshed.", $updatedTypes)); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('An error occurred while refreshing cache.')); + $this->messageManager->addExceptionMessage($e, __('An error occurred while refreshing cache.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php index f831fa67f4bb0..c10d1a77997b7 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php @@ -34,9 +34,9 @@ public function execute() foreach ($collectionsNames as $collectionName) { $this->_objectManager->create($collectionName)->aggregate(); } - $this->messageManager->addSuccess(__('We updated lifetime statistic.')); + $this->messageManager->addSuccessMessage(__('We updated lifetime statistic.')); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t refresh lifetime statistics.')); + $this->messageManager->addErrorMessage(__('We can\'t refresh lifetime statistics.')); $this->logger->critical($e); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php index 1b10c151a9d21..d95b0541c2c76 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Account/Save.php @@ -76,12 +76,12 @@ public function execute() $errors = $user->validate(); if ($errors !== true && !empty($errors)) { foreach ($errors as $error) { - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); } } else { $user->save(); $user->sendNotificationEmailsIfRequired(); - $this->messageManager->addSuccess(__('You saved the account.')); + $this->messageManager->addSuccessMessage(__('You saved the account.')); } } catch (UserLockedException $e) { $this->_auth->logout(); @@ -91,12 +91,12 @@ public function execute() } catch (ValidatorException $e) { $this->messageManager->addMessages($e->getMessages()); if ($e->getMessage()) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('An error occurred while saving account.')); + $this->messageManager->addErrorMessage(__('An error occurred while saving account.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php index 76402169f269e..21f28188cf874 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Delete.php @@ -19,11 +19,11 @@ public function execute() try { $design->delete(); - $this->messageManager->addSuccess(__('You deleted the design change.')); + $this->messageManager->addSuccessMessage(__('You deleted the design change.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("You can't delete the design change.")); + $this->messageManager->addExceptionMessage($e, __("You can't delete the design change.")); } } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php index 1f478604ced7d..0228b48f7f11e 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Design/Save.php @@ -50,9 +50,9 @@ public function execute() try { $design->save(); $this->_eventManager->dispatch('theme_save_after'); - $this->messageManager->addSuccess(__('You saved the design change.')); + $this->messageManager->addSuccessMessage(__('You saved the design change.')); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setDesignData($data); return $resultRedirect->setPath('adminhtml/*/', ['id' => $design->getId()]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php index 4fbae6abb423a..0beeb5168b6d1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store.php @@ -103,12 +103,12 @@ protected function _backupDatabase() ->setType('db') ->setPath($filesystem->getDirectoryRead(DirectoryList::VAR_DIR)->getAbsolutePath('backups')); $backupDb->createBackup($backup); - $this->messageManager->addSuccess(__('The database was backed up.')); + $this->messageManager->addSuccessMessage(__('The database was backed up.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return false; } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('We can\'t create a backup right now. Please try again later.') ); @@ -125,7 +125,7 @@ protected function _backupDatabase() */ protected function _addDeletionNotice($typeTitle) { - $this->messageManager->addNotice( + $this->messageManager->addNoticeMessage( __( 'Deleting a %1 will not delete the information associated with the %1 (e.g. categories, products, etc.)' . ', but the %1 will not be able to be restored. It is suggested that you create a database backup ' diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php index 925ae4c69ee8e..4e323be709ae1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroup.php @@ -15,13 +15,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Group::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php index b6fbd88c7669c..23364aac1f0ab 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteGroupPost.php @@ -21,11 +21,11 @@ public function execute() $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Group::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $model->getId()]); } @@ -35,12 +35,12 @@ public function execute() try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the store.')); + $this->messageManager->addSuccessMessage(__('You deleted the store.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the store. Please try again later.')); + $this->messageManager->addExceptionMessage($e, __('Unable to delete the store. Please try again later.')); } return $redirectResult->setPath('adminhtml/*/editGroup', ['group_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php index b31de6cacc5ff..c340b1ec53aa5 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStore.php @@ -15,13 +15,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Store::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store view cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store view cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php index 13b104c5ec4c0..8146bfdd41e40 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteStorePost.php @@ -22,11 +22,11 @@ public function execute() /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Store::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This store view cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This store view cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $model->getId()]); } @@ -37,12 +37,13 @@ public function execute() try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the store view.')); + $this->messageManager->addSuccessMessage(__('You deleted the store view.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the store view. Please try again later.')); + $this->messageManager + ->addExceptionMessage($e, __('Unable to delete the store view. Please try again later.')); } return $redirectResult->setPath('adminhtml/*/editStore', ['store_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php index 1f2ec4b2ba4b1..d86f57daa396c 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsite.php @@ -15,13 +15,13 @@ public function execute() { $itemId = $this->getRequest()->getParam('item_id', null); if (!($model = $this->_objectManager->create(\Magento\Store\Model\Website::class)->load($itemId))) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This website cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This website cannot be deleted.')); /** @var \Magento\Backend\Model\View\Result\Redirect $redirectResult */ $redirectResult = $this->resultRedirectFactory->create(); return $redirectResult->setPath('adminhtml/*/editWebsite', ['website_id' => $itemId]); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php index c2d24b8c41a8c..1fca5a896e050 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/DeleteWebsitePost.php @@ -23,11 +23,11 @@ public function execute() $redirectResult = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); if (!$model) { - $this->messageManager->addError(__('Something went wrong. Please try again.')); + $this->messageManager->addErrorMessage(__('Something went wrong. Please try again.')); return $redirectResult->setPath('adminhtml/*/'); } if (!$model->isCanDelete()) { - $this->messageManager->addError(__('This website cannot be deleted.')); + $this->messageManager->addErrorMessage(__('This website cannot be deleted.')); return $redirectResult->setPath('adminhtml/*/editWebsite', ['website_id' => $model->getId()]); } @@ -37,12 +37,12 @@ public function execute() try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the website.')); + $this->messageManager->addSuccessMessage(__('You deleted the website.')); return $redirectResult->setPath('adminhtml/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Unable to delete the website. Please try again later.')); + $this->messageManager->addExceptionMessage($e, __('Unable to delete the website. Please try again later.')); } return $redirectResult->setPath('*/*/editWebsite', ['website_id' => $itemId]); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php index cbc068a480865..a8651984cfa63 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/EditStore.php @@ -57,7 +57,7 @@ public function execute() if ($model->getId() || $this->_coreRegistry->registry('store_action') == 'add') { $this->_coreRegistry->register('store_data', $model); if ($this->_coreRegistry->registry('store_action') == 'edit' && $codeBase && !$model->isReadOnly()) { - $this->messageManager->addNotice($codeBase); + $this->messageManager->addNoticeMessage($codeBase); } $resultPage = $this->createPage(); if ($this->_coreRegistry->registry('store_action') == 'add') { @@ -71,7 +71,7 @@ public function execute() )); return $resultPage; } else { - $this->messageManager->addError($notExists); + $this->messageManager->addErrorMessage($notExists); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('adminhtml/*/'); diff --git a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php index 8ca783f887ec4..910511c2b275e 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/System/Store/Save.php @@ -32,7 +32,7 @@ private function processWebsiteSave($postData) } $websiteModel->save(); - $this->messageManager->addSuccess(__('You saved the website.')); + $this->messageManager->addSuccessMessage(__('You saved the website.')); return $postData; } @@ -68,7 +68,7 @@ private function processStoreSave($postData) ); } $storeModel->save(); - $this->messageManager->addSuccess(__('You saved the store view.')); + $this->messageManager->addSuccessMessage(__('You saved the store view.')); return $postData; } @@ -98,7 +98,7 @@ private function processGroupSave($postData) ); } $groupModel->save(); - $this->messageManager->addSuccess(__('You saved the store.')); + $this->messageManager->addSuccessMessage(__('You saved the store.')); return $postData; } @@ -134,10 +134,10 @@ public function execute() $redirectResult->setPath('adminhtml/*/'); return $redirectResult; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_getSession()->setPostData($postData); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while saving. Please review the error log.') ); diff --git a/app/code/Magento/Backend/Helper/Dashboard/Order.php b/app/code/Magento/Backend/Helper/Dashboard/Order.php index 9fc2c2cdb4e6f..c19c28b6e33eb 100644 --- a/app/code/Magento/Backend/Helper/Dashboard/Order.php +++ b/app/code/Magento/Backend/Helper/Dashboard/Order.php @@ -13,7 +13,7 @@ * @api * @since 100.0.2 */ -class Order extends \Magento\Backend\Helper\Dashboard\AbstractDashboard +class Order extends AbstractDashboard { /** * @var \Magento\Reports\Model\ResourceModel\Order\Collection @@ -29,32 +29,25 @@ class Order extends \Magento\Backend\Helper\Dashboard\AbstractDashboard /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection + * @param \Magento\Store\Model\StoreManagerInterface $storeManager */ public function __construct( \Magento\Framework\App\Helper\Context $context, - \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection + \Magento\Reports\Model\ResourceModel\Order\Collection $orderCollection, + \Magento\Store\Model\StoreManagerInterface $storeManager = null ) { $this->_orderCollection = $orderCollection; - parent::__construct($context); - } + $this->_storeManager = $storeManager ?: ObjectManager::getInstance() + ->get(\Magento\Store\Model\StoreManagerInterface::class); - /** - * The getter function to get the new StoreManager dependency - * - * @return \Magento\Store\Model\StoreManagerInterface - * - * @deprecated 100.1.0 - */ - private function getStoreManager() - { - if ($this->_storeManager === null) { - $this->_storeManager = ObjectManager::getInstance()->get(\Magento\Store\Model\StoreManagerInterface::class); - } - return $this->_storeManager; + parent::__construct($context); } /** * @return void + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _initCollection() { @@ -65,15 +58,15 @@ protected function _initCollection() if ($this->getParam('store')) { $this->_collection->addFieldToFilter('store_id', $this->getParam('store')); } elseif ($this->getParam('website')) { - $storeIds = $this->getStoreManager()->getWebsite($this->getParam('website'))->getStoreIds(); + $storeIds = $this->_storeManager->getWebsite($this->getParam('website'))->getStoreIds(); $this->_collection->addFieldToFilter('store_id', ['in' => implode(',', $storeIds)]); } elseif ($this->getParam('group')) { - $storeIds = $this->getStoreManager()->getGroup($this->getParam('group'))->getStoreIds(); + $storeIds = $this->_storeManager->getGroup($this->getParam('group'))->getStoreIds(); $this->_collection->addFieldToFilter('store_id', ['in' => implode(',', $storeIds)]); } elseif (!$this->_collection->isLive()) { $this->_collection->addFieldToFilter( 'store_id', - ['eq' => $this->getStoreManager()->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId()] + ['eq' => $this->_storeManager->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId()] ); } $this->_collection->load(); diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml index bcff329d79dad..9ba4430bafe35 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml index 8a24ab2a2f185..a7ef237a232b8 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml index cdaf231e9dda0..a4d922086df34 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LogoutActionGroup.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml index 9fe5f54f1db3c..6f27b03e4df30 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SecondaryGridActionGroup.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml index b7b63c5d9a62e..fd353964bae9a 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SortByIdDescendingActionGroup.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml b/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml index 286685315a7bc..016e936977cd0 100644 --- a/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml +++ b/app/code/Magento/Backend/Test/Mftf/Data/BackenedData.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> data diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml index a53938d534644..d1bf3c2cb2ed6 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationStoresPage.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml index ca0797f7ded26..b68b9914186f6 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminLoginPage.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml index 75ef114ec64b6..713199771e824 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminLogoutPage.xml @@ -7,6 +7,6 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml index dc512e66528ac..2ec25da461908 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfirmationModalSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml index 3e8f8a8f2e412..cc92e530cf3d4 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminGridTableSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml index 92b06878ab87f..441ce886f117b 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml index b65a969e334c4..3b10fac7bb9dc 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginFormSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml index f8d259cc8e490..cb164d43a49ff 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml index cce21ea760c40..b1350d5dcc1d7 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml @@ -7,9 +7,10 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml index ea84ce7ea0c4f..9051eb747a7a6 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSecondaryGridSection.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index 99d0f6654738a..7f0194b7dc347 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> diff --git a/app/code/Magento/Backend/Test/Mftf/composer.json b/app/code/Magento/Backend/Test/Mftf/composer.json deleted file mode 100644 index adc3900cfe054..0000000000000 --- a/app/code/Magento/Backend/Test/Mftf/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/functional-test-module-backend", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backup": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-developer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-translation": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php index b1911da024227..ac0f4a2f467c8 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanMediaTest.php @@ -38,7 +38,7 @@ public function testExecute() $messageManagerParams = $helper->getConstructArguments(\Magento\Framework\Message\Manager::class); $messageManagerParams['exceptionMessageFactory'] = $exceptionMessageFactory; $messageManager = $this->getMockBuilder(\Magento\Framework\Message\Manager::class) - ->setMethods(['addSuccess']) + ->setMethods(['addSuccessMessage']) ->setConstructorArgs($messageManagerParams) ->getMock(); @@ -86,7 +86,7 @@ public function testExecute() $mergeService->expects($this->once())->method('cleanMergedJsCss'); $messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The JavaScript/CSS cache has been cleaned.'); $valueMap = [ diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php index 40d9ca1aa8996..fc457cd9681e6 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/CleanStaticFilesTest.php @@ -76,7 +76,7 @@ public function testExecute() ->with('clean_static_files_cache_after'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The static files cache has been cleaned.'); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php index 197b46acc61bc..a8b248c611e07 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassDisableTest.php @@ -156,7 +156,7 @@ public function testExecuteInvalidTypeCache() ->willReturn(['someCache']); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('These cache type(s) don\'t exist: someCache') ->willReturnSelf(); @@ -176,7 +176,7 @@ public function testExecuteWithException() ->willThrowException($exception); $this->messageManagerMock->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, 'An error occurred while disabling cache.') ->willReturnSelf(); @@ -216,7 +216,7 @@ public function testExecuteSuccess() ->method('persist'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('1 cache type(s) disabled.') ->willReturnSelf(); diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php index 9b3640193154a..6eac44a564f6d 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Cache/MassEnableTest.php @@ -156,7 +156,7 @@ public function testExecuteInvalidTypeCache() ->willReturn(['someCache']); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('These cache type(s) don\'t exist: someCache') ->willReturnSelf(); @@ -176,7 +176,7 @@ public function testExecuteWithException() ->willThrowException($exception); $this->messageManagerMock->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, 'An error occurred while enabling cache.') ->willReturnSelf(); @@ -216,7 +216,7 @@ public function testExecuteSuccess() ->method('persist'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('1 cache type(s) enabled.') ->willReturnSelf(); diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php index e8dcc00345fc6..a985681919f0b 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/RefreshStatisticsTest.php @@ -107,7 +107,7 @@ public function testExecute() $this->resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirect); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('We updated lifetime statistic.')); $this->objectManager->expects($this->any()) diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php index 844a821df1c20..a8490d6ba2e58 100644 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php +++ b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/System/Account/SaveTest.php @@ -71,7 +71,7 @@ protected function setUp() ->getMock(); $this->_messagesMock = $this->getMockBuilder(\Magento\Framework\Message\Manager::class) ->disableOriginalConstructor() - ->setMethods(['addSuccess']) + ->setMethods(['addSuccessMessage']) ->getMockForAbstractClass(); $this->_authSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) @@ -221,7 +221,7 @@ public function testSaveAction() $this->_requestMock->setParams($requestParams); - $this->_messagesMock->expects($this->once())->method('addSuccess')->with($this->equalTo($testedMessage)); + $this->_messagesMock->expects($this->once())->method('addSuccessMessage')->with($this->equalTo($testedMessage)); $this->_controller->execute(); } diff --git a/app/code/Magento/Backend/etc/adminhtml/di.xml b/app/code/Magento/Backend/etc/adminhtml/di.xml index e08d4ac202756..d8e9674d2b4cb 100644 --- a/app/code/Magento/Backend/etc/adminhtml/di.xml +++ b/app/code/Magento/Backend/etc/adminhtml/di.xml @@ -14,6 +14,8 @@ + Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index be1b836d64802..cd32d5224ab6a 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -197,7 +197,7 @@ - + Magento\Config\Model\Config\Source\Image\Adapter Magento\Config\Model\Config\Backend\Image\Adapter @@ -314,11 +314,11 @@ Magento\Config\Model\Config\Source\Yesno - + For Windows server only. - + For Windows server only. @@ -435,7 +435,7 @@ Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third-party services (e.g. PayPal etc.).]]> - + Magento\Config\Model\Config\Source\Web\Redirect I.e. redirect from http://example.com/store/ to http://www.example.com/store/ @@ -448,7 +448,7 @@ Magento\Config\Model\Config\Source\Yesno - + Any of the fields allow fully qualified URLs that end with '/' (slash) e.g. http://example.com/magento/ @@ -456,7 +456,7 @@ Magento\Config\Model\Config\Backend\Baseurl Specify URL or {{base_url}} placeholder. - + Magento\Config\Model\Config\Backend\Baseurl May start with {{unsecure_base_url}} placeholder. @@ -466,13 +466,13 @@ Magento\Config\Model\Config\Backend\Baseurl May be empty or start with {{unsecure_base_url}} placeholder. - + Magento\Config\Model\Config\Backend\Baseurl May be empty or start with {{unsecure_base_url}} placeholder. - + Any of the fields allow fully qualified URLs that end with '/' (slash) e.g. https://example.com/magento/ @@ -480,7 +480,7 @@ Magento\Config\Model\Config\Backend\Baseurl Specify URL or {{base_url}}, or {{unsecure_base_url}} placeholder. - + Magento\Config\Model\Config\Backend\Baseurl May start with {{secure_base_url}} or {{unsecure_base_url}} placeholder. @@ -490,24 +490,24 @@ Magento\Config\Model\Config\Backend\Baseurl May be empty or start with {{secure_base_url}}, or {{unsecure_base_url}} placeholder. - + Magento\Config\Model\Config\Backend\Baseurl May be empty or start with {{secure_base_url}}, or {{unsecure_base_url}} placeholder. - + Magento\Config\Model\Config\Source\Yesno Magento\Config\Model\Config\Backend\Secure Enter https protocol to use Secure URLs on Storefront. - + Magento\Config\Model\Config\Source\Yesno Magento\Config\Model\Config\Backend\Secure Enter https protocol to use Secure URLs in Admin. - + Magento\Config\Model\Config\Source\Yesno Magento\Config\Model\Config\Backend\Secure @@ -517,7 +517,7 @@ 1 - + Magento\Config\Model\Config\Source\Yesno Magento\Config\Model\Config\Backend\Secure diff --git a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml index 8feccc9cf1b8f..f952001f5e2ff 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/page/header.phtml @@ -17,7 +17,7 @@ class="logo"> <?= $block->escapeHtml(__('Magento Admin Panel')) ?> + alt="escapeHtml(__('Magento Admin Panel')) ?>" title="escapeHtml(__('Magento Admin Panel')) ?>"/> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml index 4f8d9a56a38a3..e11c0efc123ff 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/form/element/gallery.phtml @@ -34,7 +34,7 @@ getValues()->getAttributeBackend()->getImageTypes() as $type): ?> - <?= /* @escapeNotVerified */ $image->getValue() ?>
+ <?= /* @escapeNotVerified */ $image->getValue() ?>
diff --git a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml index 46902363f1fd7..79c987383299f 100644 --- a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_form.xml @@ -92,7 +92,7 @@
- + false diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php index 27770182a6db6..53f45aff50cbc 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php @@ -82,7 +82,7 @@ public function execute() $backupManager->create(); - $this->messageManager->addSuccess($successMessage); + $this->messageManager->addSuccessMessage($successMessage); $response->setRedirectUrl($this->getUrl('*/*/index')); } catch (\Magento\Framework\Backup\Exception\NotEnoughFreeSpace $e) { diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php index 04292d2759093..90657fc2490ba 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/MassDelete.php @@ -49,13 +49,13 @@ public function execute() $resultData->setIsSuccess(true); if ($allBackupsDeleted) { - $this->messageManager->addSuccess(__('You deleted the selected backup(s).')); + $this->messageManager->addSuccessMessage(__('You deleted the selected backup(s).')); } else { throw new \Exception($deleteFailMessage); } } catch (\Exception $e) { $resultData->setIsSuccess(false); - $this->messageManager->addError($deleteFailMessage); + $this->messageManager->addErrorMessage($deleteFailMessage); } return $this->_redirect('backup/*/index'); diff --git a/app/code/Magento/Backup/Model/BackupFactory.php b/app/code/Magento/Backup/Model/BackupFactory.php index 28b0a7baf43cb..f88b410a2371b 100644 --- a/app/code/Magento/Backup/Model/BackupFactory.php +++ b/app/code/Magento/Backup/Model/BackupFactory.php @@ -39,8 +39,8 @@ public function __construct(\Magento\Framework\ObjectManagerInterface $objectMan */ public function create($timestamp, $type) { - $fsCollection = $this->_objectManager->get(\Magento\Backup\Model\Fs\Collection::class); - $backupInstance = $this->_objectManager->get(\Magento\Backup\Model\Backup::class); + $fsCollection = $this->_objectManager->create(\Magento\Backup\Model\Fs\Collection::class); + $backupInstance = $this->_objectManager->create(\Magento\Backup\Model\Backup::class); foreach ($fsCollection as $backup) { if ($backup->getTime() === (int) $timestamp && $backup->getType() === $type) { diff --git a/app/code/Magento/Backup/Model/Fs/Collection.php b/app/code/Magento/Backup/Model/Fs/Collection.php index 2d08ac04528ac..b17c17f7074fb 100644 --- a/app/code/Magento/Backup/Model/Fs/Collection.php +++ b/app/code/Magento/Backup/Model/Fs/Collection.php @@ -115,7 +115,8 @@ protected function _generateRow($filename) if (isset($row['display_name']) && $row['display_name'] == '') { $row['display_name'] = 'WebSetupWizard'; } - $row['id'] = $row['time'] . '_' . $row['type'] . (isset($row['display_name']) ? $row['display_name'] : ''); + $row['id'] = $row['time'] . '_' . $row['type'] + . (isset($row['display_name']) ? '_' . $row['display_name'] : ''); return $row; } } diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml new file mode 100644 index 0000000000000..89381112e6c66 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/CreateBackupActionGroup.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml new file mode 100644 index 0000000000000..4f34f24c3a806 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml new file mode 100644 index 0000000000000..ae97351cafcaf --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml @@ -0,0 +1,23 @@ + + + + + + systemBackup + System + + + mediaBackup + Database and Media + + + databaseBackup + Database + + diff --git a/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml b/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml new file mode 100644 index 0000000000000..aad29e6e6d566 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Page/BackupIndexPage.xml @@ -0,0 +1,16 @@ + + + + + +
+
+
+ + diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.xml new file mode 100644 index 0000000000000..af88146bcfb4b --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminCreateBackupFormSection.xml @@ -0,0 +1,18 @@ + + + + +
+ + + + + +
+
diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.xml new file mode 100644 index 0000000000000..cca6b428a2820 --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridActionSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.xml new file mode 100644 index 0000000000000..72fb51684931f --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminGridTableSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.xml new file mode 100644 index 0000000000000..11ba79f32b5db --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Section/AdminMainActionsSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml new file mode 100644 index 0000000000000..b83434f7ad1de --- /dev/null +++ b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + <description value="An admin user can create a backup of each type and delete each backup."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94176"/> + <group value="backup"/> + <group value="skip"/> + </annotations> + + <!--Login to admin area--> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + + <!--Go to backup index page--> + <amOnPage url="{{BackupIndexPage.url}}" stepKey="goToBackupPage"/> + <waitForPageLoad stepKey="waitForBackupPage"/> + + <!--Create system backup--> + <actionGroup ref="createSystemBackup" stepKey="createSystemBackup"/> + + <!--Create database/media backup--> + <actionGroup ref="createMediaBackup" stepKey="createMediaBackup"/> + + <!--Create database backup--> + <actionGroup ref="createDatabaseBackup" stepKey="createDatabaseBackup"/> + + <!--Delete system backup--> + <actionGroup ref="deleteBackup" stepKey="deleteSystemBackup"> + <argument name="backup" value="SystemBackup"/> + </actionGroup> + + <!--Delete database/media backup--> + <actionGroup ref="deleteBackup" stepKey="deleteMediaBackup"> + <argument name="backup" value="MediaBackup"/> + </actionGroup> + + <!--Delete database backup--> + <actionGroup ref="deleteBackup" stepKey="deleteDatabaseBackup"> + <argument name="backup" value="DatabaseBackup"/> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/Backup/Test/Mftf/composer.json b/app/code/Magento/Backup/Test/Mftf/composer.json deleted file mode 100644 index d7a6c804ab5b3..0000000000000 --- a/app/code/Magento/Backup/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-backup", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php b/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php index 629028bfd6f15..abf5e63276afa 100644 --- a/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php +++ b/app/code/Magento/Backup/Test/Unit/Model/BackupFactoryTest.php @@ -56,7 +56,7 @@ protected function setUp() $this->_objectManager->expects( $this->at(0) )->method( - 'get' + 'create' )->with( \Magento\Backup\Model\Fs\Collection::class )->will( @@ -65,7 +65,7 @@ protected function setUp() $this->_objectManager->expects( $this->at(1) )->method( - 'get' + 'create' )->with( \Magento\Backup\Model\Backup::class )->will( diff --git a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php new file mode 100644 index 0000000000000..0cab5f0ad1e99 --- /dev/null +++ b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php @@ -0,0 +1,243 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backup\Test\Unit\Model; + +use Magento\Backup\Model\Db; +use Magento\Backup\Model\ResourceModel\Db as DbResource; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Backup\Db\BackupInterface; +use Magento\Framework\DataObject; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class DbTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Db + */ + private $dbModel; + + /** + * @var DbResource|\PHPUnit_Framework_MockObject_MockObject + */ + private $dbResourceMock; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionResourceMock; + + protected function setUp() + { + $this->dbResourceMock = $this->getMockBuilder(DbResource::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionResourceMock = $this->getMockBuilder(ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new ObjectManager($this); + $this->dbModel = $this->objectManager->getObject( + Db::class, + [ + 'resourceDb' => $this->dbResourceMock, + 'resource' => $this->connectionResourceMock + ] + ); + } + + public function testGetResource() + { + self::assertEquals($this->dbResourceMock, $this->dbModel->getResource()); + } + + public function testGetTables() + { + $tables = []; + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + + self::assertEquals($tables, $this->dbModel->getTables()); + } + + public function testGetTableCreateScript() + { + $tableName = 'some_table'; + $script = 'script'; + $this->dbResourceMock->expects($this->once()) + ->method('getTableCreateScript') + ->with($tableName, false) + ->willReturn($script); + + self::assertEquals($script, $this->dbModel->getTableCreateScript($tableName, false)); + } + + public function testGetTableDataDump() + { + $tableName = 'some_table'; + $dump = 'dump'; + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataDump') + ->with($tableName) + ->willReturn($dump); + + self::assertEquals($dump, $this->dbModel->getTableDataDump($tableName)); + } + + public function testGetHeader() + { + $header = 'header'; + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + + self::assertEquals($header, $this->dbModel->getHeader()); + } + + public function testGetFooter() + { + $footer = 'footer'; + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + + self::assertEquals($footer, $this->dbModel->getFooter()); + } + + public function testRenderSql() + { + $header = 'header'; + $script = 'script'; + $tableName = 'some_table'; + $tables = [$tableName, $tableName]; + $dump = 'dump'; + $footer = 'footer'; + + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->exactly(2)) + ->method('getTableCreateScript') + ->with($tableName, true) + ->willReturn($script); + $this->dbResourceMock->expects($this->exactly(2)) + ->method('getTableDataDump') + ->with($tableName) + ->willReturn($dump); + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + + self::assertEquals( + $header . $script . $dump . $script . $dump . $footer, + $this->dbModel->renderSql() + ); + } + + public function testCreateBackup() + { + /** @var BackupInterface|\PHPUnit_Framework_MockObject_MockObject $backupMock */ + $backupMock = $this->getMockBuilder(BackupInterface::class)->getMock(); + /** @var DataObject $tableStatus */ + $tableStatus = new DataObject(); + + $tableName = 'some_table'; + $tables = [$tableName]; + $header = 'header'; + $footer = 'footer'; + $dropSql = 'drop_sql'; + $createSql = 'create_sql'; + $beforeSql = 'before_sql'; + $afterSql = 'after_sql'; + $dataSql = 'data_sql'; + $foreignKeysSql = 'foreign_keys'; + $triggersSql = 'triggers_sql'; + $rowsCount = 2; + $dataLength = 1; + + $this->dbResourceMock->expects($this->once()) + ->method('beginTransaction'); + $this->dbResourceMock->expects($this->once()) + ->method('commitTransaction'); + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($tables); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDropSql') + ->willReturn($dropSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableCreateSql') + ->with($tableName, false) + ->willReturn($createSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataBeforeSql') + ->with($tableName) + ->willReturn($beforeSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataAfterSql') + ->with($tableName) + ->willReturn($afterSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableDataSql') + ->with($tableName, $rowsCount, 0) + ->willReturn($dataSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableStatus') + ->with($tableName) + ->willReturn($tableStatus); + $this->dbResourceMock->expects($this->once()) + ->method('getTables') + ->willReturn($createSql); + $this->dbResourceMock->expects($this->once()) + ->method('getHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->once()) + ->method('getTableHeader') + ->willReturn($header); + $this->dbResourceMock->expects($this->once()) + ->method('getFooter') + ->willReturn($footer); + $this->dbResourceMock->expects($this->once()) + ->method('getTableForeignKeysSql') + ->willReturn($foreignKeysSql); + $this->dbResourceMock->expects($this->once()) + ->method('getTableTriggersSql') + ->willReturn($triggersSql); + $backupMock->expects($this->once()) + ->method('open'); + $backupMock->expects($this->once()) + ->method('close'); + + $tableStatus->setRows($rowsCount); + $tableStatus->setDataLength($dataLength); + + $backupMock->expects($this->any()) + ->method('write') + ->withConsecutive( + [$this->equalTo($header)], + [$this->equalTo($header . $dropSql . "\n")], + [$this->equalTo($createSql . "\n")], + [$this->equalTo($beforeSql)], + [$this->equalTo($dataSql)], + [$this->equalTo($afterSql)], + [$this->equalTo($foreignKeysSql)], + [$this->equalTo($triggersSql)], + [$this->equalTo($footer)] + ); + + $this->dbModel->createBackup($backupMock); + } +} diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php index 4c3f1e179d378..6d43929db7675 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionRefund.php @@ -17,8 +17,6 @@ class TransactionRefund extends AbstractTransaction protected function process(array $data) { $storeId = $data['store_id'] ?? null; - // sending store id and other additional keys are restricted by Braintree API - unset($data['store_id']); return $this->adapterFactory->create($storeId) ->refund($data['transaction_id'], $data[PaymentDataBuilder::AMOUNT]); diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php index 16ebd7a7a00c5..6760e724fd3a6 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionSubmitForSettlement.php @@ -19,8 +19,6 @@ class TransactionSubmitForSettlement extends AbstractTransaction protected function process(array $data) { $storeId = $data['store_id'] ?? null; - // sending store id and other additional keys are restricted by Braintree API - unset($data['store_id']); return $this->adapterFactory->create($storeId) ->submitForSettlement($data[CaptureDataBuilder::TRANSACTION_ID], $data[PaymentDataBuilder::AMOUNT]); diff --git a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php index e9a478a8bac23..0a940839fc154 100644 --- a/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php +++ b/app/code/Magento/Braintree/Gateway/Http/Client/TransactionVoid.php @@ -15,8 +15,6 @@ class TransactionVoid extends AbstractTransaction protected function process(array $data) { $storeId = $data['store_id'] ?? null; - // sending store id and other additional keys are restricted by Braintree API - unset($data['store_id']); return $this->adapterFactory->create($storeId)->void($data['transaction_id']); } diff --git a/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php new file mode 100644 index 0000000000000..6dc40e76322df --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Request/MerchantAccountDataBuilder.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Gateway\Request; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Request\BuilderInterface; + +/** + * Adds Merchant Account ID to the request if it was specified in the configuration. + */ +class MerchantAccountDataBuilder implements BuilderInterface +{ + /** + * The merchant account ID used to create a transaction. + * Currency is also determined by merchant account ID. + * If no merchant account ID is specified, Braintree will use your default merchant account. + */ + private static $merchantAccountId = 'merchantAccountId'; + + /** + * @var Config + */ + private $config; + + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * Constructor + * + * @param Config $config + * @param SubjectReader $subjectReader + */ + public function __construct(Config $config, SubjectReader $subjectReader) + { + $this->config = $config; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + $result = []; + $merchantAccountId = $this->config->getMerchantAccountId($order->getStoreId()); + if (!empty($merchantAccountId)) { + $result[self::$merchantAccountId] = $merchantAccountId; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php index 85a0c64451398..fe75ce86cca2f 100644 --- a/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/PaymentDataBuilder.php @@ -6,8 +6,8 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Observer\DataAssignObserver; use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; @@ -36,9 +36,8 @@ class PaymentDataBuilder implements BuilderInterface const PAYMENT_METHOD_NONCE = 'paymentMethodNonce'; /** - * The merchant account ID used to create a transaction. - * Currency is also determined by merchant account ID. - * If no merchant account ID is specified, Braintree will use your default merchant account. + * @deprecated + * @see \Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder */ const MERCHANT_ACCOUNT_ID = 'merchantAccountId'; @@ -47,25 +46,18 @@ class PaymentDataBuilder implements BuilderInterface */ const ORDER_ID = 'orderId'; - /** - * @var Config - */ - private $config; - /** * @var SubjectReader */ private $subjectReader; /** - * Constructor - * * @param Config $config * @param SubjectReader $subjectReader + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct(Config $config, SubjectReader $subjectReader) { - $this->config = $config; $this->subjectReader = $subjectReader; } @@ -87,11 +79,6 @@ public function build(array $buildSubject) self::ORDER_ID => $order->getOrderIncrementId() ]; - $merchantAccountId = $this->config->getMerchantAccountId($order->getStoreId()); - if (!empty($merchantAccountId)) { - $result[self::MERCHANT_ACCOUNT_ID] = $merchantAccountId; - } - return $result; } } diff --git a/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php index e89e604867baa..32abeac4c8ffb 100644 --- a/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/CardDetailsHandler.php @@ -85,7 +85,7 @@ public function handle(array $handlingSubject, array $response) private function getCreditCardType($type) { $replaced = str_replace(' ', '-', strtolower($type)); - $mapper = $this->config->getCctypesMapper(); + $mapper = $this->config->getCcTypesMapper(); return $mapper[$replaced]; } diff --git a/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php b/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php index 7c307185b4c22..8880f9c1b1a3e 100644 --- a/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php +++ b/app/code/Magento/Braintree/Gateway/Response/VaultDetailsHandler.php @@ -157,7 +157,7 @@ private function convertDetailsToJSON($details) private function getCreditCardType($type) { $replaced = str_replace(' ', '-', strtolower($type)); - $mapper = $this->config->getCctypesMapper(); + $mapper = $this->config->getCcTypesMapper(); return $mapper[$replaced]; } diff --git a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php index fe30e790de07c..928769498a035 100644 --- a/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/ConfigProvider.php @@ -70,7 +70,7 @@ public function getConfig() self::CODE => [ 'isActive' => $this->config->isActive($storeId), 'clientToken' => $this->getClientToken(), - 'ccTypesMapper' => $this->config->getCctypesMapper(), + 'ccTypesMapper' => $this->config->getCcTypesMapper(), 'sdkUrl' => $this->config->getSdkUrl(), 'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig($storeId), 'availableCardTypes' => $this->config->getAvailableCardTypes($storeId), diff --git a/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php b/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php index a0704002842ea..d08bf62da8e4f 100644 --- a/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php +++ b/app/code/Magento/Braintree/Setup/Patch/Data/ConvertSerializedDataToJson.php @@ -12,7 +12,7 @@ use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Convert data fro php native serialized data to JSON. + * Convert data from php native serialized data to JSON. */ class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInterface { diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml new file mode 100644 index 0000000000000..e86d5403e11eb --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="GoToUserRoles"> + <click selector="#menu-magento-backend-system" stepKey="clickOnSystemIcon"/> + <waitForPageLoad stepKey="waitForSystemsPageToOpen"/> + <click selector="//span[contains(text(), 'User Roles')]" stepKey="clickToSelectUserRoles"/> + <waitForPageLoad stepKey="waitForUserRolesPageToOpen"/> + </actionGroup> + + <!--Create new role--> + <actionGroup name="AdminCreateRole"> + <arguments> + <argument name="role" type="string" defaultValue=""/> + <argument name="resource" type="string" defaultValue="All"/> + <argument name="scope" type="string" defaultValue="Custom"/> + <argument name="websites" type="string" defaultValue="Main Website"/> + </arguments> + <click selector="{{AdminCreateRoleSection.create}}" stepKey="clickToAddNewRole"/> + <fillField selector="{{AdminCreateRoleSection.name}}" userInput="{{role.name}}" stepKey="setRoleName"/> + <fillField stepKey="setPassword" selector="{{AdminCreateRoleSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click selector="{{AdminCreateRoleSection.roleResources}}" stepKey="clickToOpenRoleResources"/> + <waitForPageLoad stepKey="waitForRoleResourcePage" time="5"/> + <click stepKey="checkSales" selector="//a[text()='Sales']"/> + <click selector="{{AdminCreateRoleSection.save}}" stepKey="clickToSaveRole"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see userInput="You saved the role." stepKey="seeSuccessMessage" /> + </actionGroup> + + + <!--Delete role--> + <actionGroup name="AdminDeleteRoleActionGroup"> + <arguments> + <argument name="role" defaultValue=""/> + </arguments> + <click stepKey="clickOnRole" selector="{{AdminDeleteRoleSection.theRole}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteRoleSection.current_pass}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click stepKey="clickToDeleteRole" selector="{{AdminDeleteRoleSection.delete}}"/> + <waitForAjaxLoad stepKey="waitForDeleteConfirmationPopup" time="5"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteRoleSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see stepKey="seeSuccessMessage" userInput="You deleted the role."/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml new file mode 100644 index 0000000000000..23c322083773c --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Go to all users--> + <actionGroup name="GoToAllUsers"> + <click selector="{{AdminCreateUserSection.system}}" stepKey="clickOnSystemIcon"/> + <waitForPageLoad stepKey="waitForSystemsPageToOpen"/> + <click selector="{{AdminCreateUserSection.allUsers}}" stepKey="clickToSelectUserRoles"/> + <waitForPageLoad stepKey="waitForUserRolesPageToOpen"/> + </actionGroup> + + <!--Create new user with specified role--> + <actionGroup name="AdminCreateUserAction"> + <click selector="{{AdminCreateUserSection.create}}" stepKey="clickToCreateNewUser"/> + <waitForPageLoad stepKey="waitForNewUserPageLoad" time="10"/> + <fillField selector="{{AdminCreateUserSection.usernameTextField}}" userInput="{{NewAdmin.username}}" stepKey="enterUserName" /> + <fillField selector="{{AdminCreateUserSection.firstNameTextField}}" userInput="{{NewAdmin.firstName}}" stepKey="enterFirstName" /> + <fillField selector="{{AdminCreateUserSection.lastNameTextField}}" userInput="{{NewAdmin.lastName}}" stepKey="enterLastName" /> + <fillField selector="{{AdminCreateUserSection.emailTextField}}" userInput="{{NewAdmin.email}}" stepKey="enterEmail" /> + <fillField selector="{{AdminCreateUserSection.passwordTextField}}" userInput="{{NewAdmin.password}}" stepKey="enterPassword" /> + <fillField selector="{{AdminCreateUserSection.pwConfirmationTextField}}" userInput="{{NewAdmin.password}}" stepKey="confirmPassword" /> + <fillField selector="{{AdminCreateUserSection.currentPasswordField}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterCurrentPassword" /> + <scrollToTopOfPage stepKey="scrollToTopOfPage" /> + <click selector="{{AdminCreateUserSection.userRoleTab}}" stepKey="clickUserRole" /> + <waitForAjaxLoad stepKey="waitForRoles" time="5"/> + <fillField selector="{{AdminCreateRoleSection.roleNameFilterTextField}}" userInput="{{role.name}}" stepKey="filterRole" /> + <click selector="{{AdminCreateRoleSection.searchButton}}" stepKey="clickSearch" /> + <waitForPageLoad stepKey="waitForSearch" time="10"/> + <click selector="{{AdminCreateRoleSection.searchResultFirstRow}}" stepKey="selectRole" /> + <click selector="{{AdminCreateUserSection.saveButton}}" stepKey="clickSaveUser" /> + <waitForPageLoad stepKey="waitForSaveUser" time="10"/> + <see userInput="You saved the user." stepKey="seeSuccessMessage" /> + </actionGroup> + + + <!--Delete User--> + <actionGroup name="AdminDeleteUserActionGroup"> + + <click stepKey="clickOnUser" selector="{{AdminDeleteUserSection.theUser}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeletePopupOpen" time="5"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteUserSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see userInput="You deleted the user." stepKey="seeSuccessMessage" /> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml new file mode 100644 index 0000000000000..27e2039fe526e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="ConfigureBraintree"> + <!-- GoTo ConfigureBraintree fields --> + <click stepKey="clickOnSTORES" selector="{{AdminMenuSection.stores}}"/> + <waitForPageLoad stepKey="waitForConfiguration" time="2"/> + <click stepKey="clickOnConfigurations" selector="{{StoresSubmenuSection.configuration}}" /> + <waitForPageLoad stepKey="waitForSales" time="2"/> + <click stepKey="clickOnSales" selector="{{ConfigurationListSection.sales}}" /> + <waitForPageLoad stepKey="waitForPaymentMethods" time="2"/> + <click stepKey="clickOnPaymentMethods" selector="{{ConfigurationListSection.salesPaymentMethods}}" /> + <waitForPageLoad stepKey="waitForConfigureButton" time="2"/> + <click stepKey="clickOnConfigureButtonForBraintree" selector="{{ConfigurationPaymentSection.configureButton}}" /> + <waitForPageLoad stepKey="BraintreeSettings" time="2"/> + + <!-- Fill Braintree fields --> + <fillField stepKey="fillTitleForBraintreeSettings" selector="{{BraintreeConfiguraionSection.titleForBraintreeSettings}}" userInput="{{BraintreeConfigurationData.title}}"/> + <click stepKey="openEnvironmentSelect" selector="{{BraintreeConfiguraionSection.environment}}"/> + <click stepKey="chooseEnvironment" selector="{{BraintreeConfiguraionSection.sandbox}}"/> + <click stepKey="openPaymentActionSelect" selector="{{BraintreeConfiguraionSection.paymentActionSelect}}"/> + <click stepKey="choosePaymentAction" selector="{{BraintreeConfiguraionSection.paymentAction}}"/> + <fillField stepKey="fillMerchantID" selector="{{BraintreeConfiguraionSection.merchantID}}" userInput="{{BraintreeConfigurationData.merchantID}}"/> + <fillField stepKey="fillPublicKey" selector="{{BraintreeConfiguraionSection.publicKey}}" userInput="{{BraintreeConfigurationData.publicKey}}"/> + <fillField stepKey="fillPrivateKey" selector="{{BraintreeConfiguraionSection.privateKey}}" userInput="{{BraintreeConfigurationData.privateKey}}"/> + <click stepKey="expandEnableThisSolution" selector="{{BraintreeConfiguraionSection.enableThisSolution}}"/> + <click stepKey="chooseYesForEnableThisSolution" selector="{{BraintreeConfiguraionSection.yesForEnable}}"/> + <click stepKey="expandEnablePayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.payPalThroughBraintree}}"/> + <click stepKey="chooseYesForEnablePayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.yesForPayPalThroughBraintree}}"/> + <click stepKey="expandAdvancedBraintreeSettings" selector="{{BraintreeConfiguraionSection.advancedBraintreeSettings}}"/> + <fillField stepKey="fillMerchantAccountID" selector="{{BraintreeConfiguraionSection.merchantAccountID}}" userInput="{{BraintreeConfigurationData.merchantAccountID}}"/> + <click stepKey="expandCVVVerification" selector="{{BraintreeConfiguraionSection.CVVVerification}}"/> + <click stepKey="chooseYes" selector="{{BraintreeConfiguraionSection.yesForCVV}}"/> + <click stepKey="expandPayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.payPalThroughBraintreeSelector}}"/> + <fillField stepKey="fillTitleForPayPalThroughBraintree" selector="{{BraintreeConfiguraionSection.titleForPayPalThroughBraintree}}" userInput="{{BraintreeConfigurationData.titleForPayPalThroughBraintree}}"/> + <click stepKey="expandPaymentAction" selector="{{BraintreeConfiguraionSection.paymentActionInPayPal}}"/> + <click stepKey="chooseAuthorize" selector="{{BraintreeConfiguraionSection.actionAuthorize}}"/> + <click stepKey="save" selector="{{BraintreeConfiguraionSection.save}}"/> + <waitForElementVisible selector="{{BraintreeConfiguraionSection.successfulMessage}}" stepKey="waitForSuccessfullyConfigured" time="10"/> + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml new file mode 100644 index 0000000000000..a68042127ec48 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateCustomerActionGroup"> + <click stepKey="openCustomers" selector="{{AdminMenuSection.customers}}"/> + <waitForAjaxLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnAllCustomers" selector="{{CustomersSubmenuSection.allCustomers}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="addNewCustomer" selector="{{CustomersPageSection.addNewCustomerButton}}"/> + <waitForPageLoad stepKey="waitForNewProductPage" time="10"/> + <click stepKey="AssociateToWebsite" selector="{{NewCustomerPageSection.associateToWebsite}}"/> + <click stepKey="Group" selector="{{NewCustomerPageSection.group}}"/> + <fillField stepKey="FillFirstName" selector="{{NewCustomerPageSection.firstName}}" userInput="{{NewCustomerData.FirstName}}"/> + <fillField stepKey="FillLastName" selector="{{NewCustomerPageSection.lastName}}" userInput="{{NewCustomerData.LastName}}"/> + <fillField stepKey="FillEmail" selector="{{NewCustomerPageSection.email}}" userInput="{{NewCustomerData.Email}}"/> + <scrollToTopOfPage stepKey="scrollToAddresses"/> + <click stepKey="goToAddresses" selector="{{NewCustomerPageSection.addresses}}"/> + <waitForAjaxLoad stepKey="waitForAddresses" time="5"/> + <click stepKey="AddNewAddress" selector="{{NewCustomerPageSection.addNewAddress}}"/> + <waitForPageLoad stepKey="waitForAddressFields" time="5"/> + <click stepKey="thickBillingAddress" selector="{{NewCustomerPageSection.defaultBillingAddress}}"/> + <click stepKey="thickShippingAddress" selector="{{NewCustomerPageSection.defaultShippingAddress}}"/> + <fillField stepKey="fillFirstNameForAddress" selector="{{NewCustomerPageSection.firstNameForAddress}}" userInput="{{NewCustomerData.AddressFirstName}}"/> + <fillField stepKey="fillLastNameForAddress" selector="{{NewCustomerPageSection.lastNameForAddress}}" userInput="{{NewCustomerData.AddressLastName}}"/> + <fillField stepKey="fillStreetAddress" selector="{{NewCustomerPageSection.streetAddress}}" userInput="{{NewCustomerData.StreetAddress}}"/> + <fillField stepKey="fillCity" selector="{{NewCustomerPageSection.city}}" userInput="{{NewCustomerData.City}}"/> + <click stepKey="openCountry" selector="{{NewCustomerPageSection.country}}"/> + <waitForAjaxLoad stepKey="waitForCountryList" time="5"/> + <click stepKey="chooseCountry" selector="{{NewCustomerPageSection.countryArmenia}}"/> + <fillField stepKey="fillZip" selector="{{NewCustomerPageSection.zip}}" userInput="{{NewCustomerData.Zip}}"/> + <fillField stepKey="fillPhoneNumber" selector="{{NewCustomerPageSection.phoneNumber}}" userInput="{{NewCustomerData.PhoneNumber}}"/> + <waitForPageLoad stepKey="wait" time="10"/> + <click stepKey="save" selector="{{NewCustomerPageSection.saveCustomer}}"/> + <waitForPageLoad stepKey="waitForCustomersPage" time="10"/> + <waitForElementVisible selector="{{NewCustomerPageSection.createdSuccessMessage}}" stepKey="waitForSuccessfullyCreatedMessage" time="20"/> + + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml new file mode 100644 index 0000000000000..ee7158c2b63f7 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="CreateNewOrderActionGroup"> + <click stepKey="createNewOrder" selector="{{NewOrderSection.createNewOrder}}"/> + <waitForPageLoad stepKey="waitForCustomersList" time="3"/> + <click stepKey="chooseCustomer" selector="{{NewOrderSection.customer}}"/> + <waitForPageLoad stepKey="waitForOrderPage" time="3"/> + <click stepKey="addProducts" selector="{{NewOrderSection.addProducts}}"/> + <waitForPageLoad stepKey="waitForProducts" time="3"/> + <click stepKey="chooseProducts" selector="{{NewOrderSection.chooseProduct}}"/> + <waitForPageLoad stepKey="waitForProductChoose" time="3"/> + <click stepKey="addSelectedProduct" selector="{{NewOrderSection.addSelectedProduct}}"/> + <waitForAjaxLoad stepKey="waitForChoose" time="3"/> + <click stepKey="openAddresses" selector="{{NewOrderSection.openAddresses}}"/> + <click stepKey="chooseAddress" selector="{{NewOrderSection.chooseAddress}}"/> + <fillField stepKey="fillState" selector="{{NewOrderSection.state}}" userInput="Yerevan"/> + <scrollTo stepKey="scrollTo" selector="#order-methods"/> + <waitForPageLoad stepKey="waitForMethods" time="3"/> + <click stepKey="startJSMethodExecution" selector="{{NewOrderSection.openShippingMethods}}"/> + <waitForPageLoad stepKey="waitForJSMethodExecution" time="3"/> + <click stepKey="openShippingMethods" selector="{{NewOrderSection.openShippingMethods}}"/> + <waitForPageLoad stepKey="waitForShippingMethods" time="3"/> + <click stepKey="chooseShippingMethods" selector="{{NewOrderSection.shippingMethod}}"/> + <waitForPageLoad stepKey="waitForShippingMethodChoose" time="4"/> + <click stepKey="chooseBraintree" selector="{{NewOrderSection.creditCardBraintree}}"/> + <waitForPageLoad stepKey="waitForBraintreeConfigs" time="5"/> + <click stepKey="openCardTypes" selector="{{NewOrderSection.openCardTypes}}"/> + <waitForPageLoad stepKey="waitForCardTypes" time="3"/> + <click stepKey="chooseCardType" selector="{{NewOrderSection.masterCard}}"/> + <waitForPageLoad stepKey="waitForCardSelected" time="3"/> + + <switchToIFrame stepKey="switchToCardNumber" selector="{{NewOrderSection.cardFrame}}"/> + <fillField stepKey="fillCardNumber" selector="{{NewOrderSection.creditCardNumber}}" userInput="{{PaymentAndShippingInfo.cardNumber}}"/> + <waitForPageLoad stepKey="waitForFillCardNumber" time="1"/> + <switchToIFrame stepKey="switchBackFromCard"/> + + <switchToIFrame stepKey="switchToExpirationMonth" selector="{{NewOrderSection.monthFrame}}"/> + <fillField stepKey="fillMonth" selector="{{NewOrderSection.expirationMonth}}" userInput="{{PaymentAndShippingInfo.month}}"/> + <waitForPageLoad stepKey="waitForFillMonth" time="1"/> + <switchToIFrame stepKey="switchBackFromMonth"/> + + <switchToIFrame stepKey="switchToExpirationYear" selector="{{NewOrderSection.yearFrame}}"/> + <fillField stepKey="fillYear" selector="{{NewOrderSection.expirationYear}}" userInput="{{PaymentAndShippingInfo.year}}"/> + <waitForPageLoad stepKey="waitForFillYear" time="1"/> + <switchToIFrame stepKey="switchBackFromYear"/> + + <switchToIFrame stepKey="switchToCVV" selector="{{NewOrderSection.cvvFrame}}"/> + <fillField stepKey="fillCVV" selector="{{NewOrderSection.cvv}}" userInput="{{PaymentAndShippingInfo.cvv}}"/> + <wait stepKey="waitForFillCVV" time="1"/> + <switchToIFrame stepKey="switchBackFromCVV"/> + + <click stepKey="submitOrder" selector="{{NewOrderSection.submitOrder}}"/> + <waitForPageLoad stepKey="waitForSaveConfig" time="5"/> + <waitForElementVisible selector="{{NewOrderSection.successMessage}}" stepKey="waitForSuccessMessage" time="1"/> + + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml new file mode 100644 index 0000000000000..19de3e859ae9a --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateNewProductActionGroup"> + + <click stepKey="openCatalog" selector="{{AdminMenuSection.catalog}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnProducts" selector="{{CatalogSubmenuSection.products}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="addProduct" selector="{{ProductsPageSection.addProductButton}}"/> + <waitForPageLoad stepKey="waitForNewProductPage" time="10"/> + <fillField stepKey="FillProductName" selector="{{NewProductPageSection.productName}}" userInput="{{NewProductData.ProductName}}"/> + <fillField stepKey="FillPrice" selector="{{NewProductPageSection.price}}" userInput="{{NewProductData.Price}}"/> + <fillField stepKey="FillQuantity" selector="{{NewProductPageSection.quantity}}" userInput="{{NewProductData.Quantity}}"/> + <click stepKey="Save" selector="{{NewProductPageSection.saveButton}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyCreatedMessage" selector="{{NewProductPageSection.createdSuccessMessage}}" time="10"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml new file mode 100644 index 0000000000000..65eddc0d9e51a --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteCustomerActionGroup"> + <arguments> + <argument name="lastName" defaultValue=""/> + </arguments> + <click stepKey="openCustomers" selector="{{AdminMenuSection.customers}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="10"/> + <click stepKey="clickOnAllCustomers" selector="{{CustomersSubmenuSection.allCustomers}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="chooseCustomer" selector="{{CustomersPageSection.customerCheckbox(lastName)}}"/> + <waitForAjaxLoad stepKey="waitForThick" time="2"/> + <click stepKey="OpenActions" selector="{{CustomersPageSection.actions}}"/> + <waitForAjaxLoad stepKey="waitForDelete" time="5"/> + <click stepKey="ChooseDelete" selector="{{CustomersPageSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> + <click stepKey="clickOnOk" selector="{{CustomersPageSection.ok}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{CustomersPageSection.deletedSuccessMessage}}" time="10"/> + + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml new file mode 100644 index 0000000000000..724c6d92846c4 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteProductActionGroup"> + <arguments> + <argument name="productName" defaultValue=""/> + </arguments> + <click stepKey="openCatalog" selector="{{AdminMenuSection.catalog}}"/> + <waitForPageLoad stepKey="waitForCatalogSubmenu" time="5"/> + <click stepKey="clickOnProducts" selector="{{CatalogSubmenuSection.products}}"/> + <waitForPageLoad stepKey="waitForProductsPage" time="10"/> + <click stepKey="TickCheckbox" selector="{{ProductsPageSection.checkboxForProduct(productName)}}"/> + <click stepKey="OpenActions" selector="{{ProductsPageSection.actions}}"/> + <waitForAjaxLoad stepKey="waitForDelete" time="5"/> + <click stepKey="ChooseDelete" selector="{{ProductsPageSection.delete}}"/> + <waitForPageLoad stepKey="waitForDeleteItemPopup" time="10"/> + <click stepKey="clickOnOk" selector="{{ProductsPageSection.ok}}"/> + <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{ProductsPageSection.deletedSuccessMessage}}" time="10"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml new file mode 100644 index 0000000000000..7c774a634b369 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Sign out--> + <actionGroup name="SignOut"> + <click selector="{{SignOutSection.admin}}" stepKey="clickToAdminProfile"/> + <click selector="{{SignOutSection.logout}}" stepKey="clickToLogOut"/> + <waitForPageLoad stepKey="waitForPageLoad" /> + <see userInput="You have logged out." stepKey="seeSuccessMessage" /> + <waitForElementVisible selector="//*[@data-ui-id='messages-message-success']" stepKey="waitForSuccessMessageLoggedOut" time="5"/> + </actionGroup> + + <!--Login New User--> + <actionGroup name="LoginNewUser"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> + <fillField userInput="{{NewAdmin.username}}" selector="{{LoginFormSection.username}}" stepKey="fillUsername"/> + <fillField userInput="{{NewAdmin.password}}" selector="{{LoginFormSection.password}}" stepKey="fillPassword"/> + <click selector="{{LoginFormSection.signIn}}" stepKey="clickLogin"/> + </actionGroup> + +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml index 6e669a1b8bf4b..8e55362a6cba4 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SampleBraintreeConfig" type="braintree_config_state"> <requiredEntity type="title">SampleTitle</requiredEntity> <requiredEntity type="payment_action">SamplePaymentAction</requiredEntity> @@ -62,4 +62,40 @@ <entity name="DefaultPrivateKey" type="private_key"> <data key="value"/> </entity> + + <entity name="testData" type="data"> + <data key="websiteName" unique="suffix">Website</data> + <data key="websiteCode" unique="suffix">new_website</data> + <data key="name" unique="suffix">Store</data> + <data key="storeCode" unique="suffix">new_store</data> + <data key="block" unique="suffix">Block</data> + </entity> + + <entity name="role" type="data"> + <data key="name" unique="suffix">Role</data> + </entity> + <entity name="NewAdmin" type="user"> + <data key="username" unique="suffix">admin</data> + <data key="firstName">John</data> + <data key="lastName">Smith</data> + <data key="password">admin123</data> + <data key="email">mail@mail.com</data> + </entity> + + <entity name="PaymentAndShippingInfo" type="data"> + <data key="cardNumber">5105105105105100</data> + <data key="month">12</data> + <data key="year">20</data> + <data key="cvv">113</data> + </entity> + + <entity name="BraintreeConfigurationData" type="data"> + <data key="title">Credit Card (Braintree)</data> + <data key="merchantID">d4pdjhxgjfrsmzbf</data> + <data key="publicKey">m7q4wmh43xrgyrst</data> + <data key="privateKey">67de364080b1b4e2492d7a3de413a572</data> + <data key="merchantAccountID">Magneto</data> + <data key="titleForPayPalThroughBraintree">PayPal (Braintree)</data> + </entity> + </entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml new file mode 100644 index 0000000000000..772c1c39a04ca --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewCustomerData" type="braintree_config_state"> + <data key="FirstName">Abgar</data> + <data key="LastName">Abgaryan</data> + <data key="Email">m@m.com</data> + <data key="AddressFirstName">Abgar</data> + <data key="AddressLastName">Abgaryan</data> + <data key="StreetAddress">Street</data> + <data key="City">Yerevan</data> + <data key="Zip">9999</data> + <data key="PhoneNumber">9999</data> + </entity> + +</entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml new file mode 100644 index 0000000000000..72661ae94076f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewProductData" type="braintree_config_state"> + <data key="ProductName">ProductTest</data> + <data key="Price">100</data> + <data key="Quantity">100</data> + </entity> + +</entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml index e4d02a58b5bf4..83018852bfeb5 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateBraintreeConfigState" dataType="braintree_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> <object key="groups" dataType="braintree_config_state"> <object key="braintree_section" dataType="braintree_config_state"> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml new file mode 100644 index 0000000000000..1158f471d51f0 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCreateRoleSection"> + <element name="create" type="button" selector="#add"/> + <element name="name" type="button" selector="#role_name"/> + <element name="password" type="input" selector="#current_password"/> + <element name="roleResources" type="button" selector="#role_info_tabs_account"/> + <element name="roleResource" type="button" selector="#gws_is_all"/> + <element name="resourceValue" type="button" selector="//*[text()='{{arg1}}']" parameterized="true"/> + <element name="roleScope" type="button" selector="#all"/> + <element name="scopeValue" type="button" selector="//select[@id='all']/*[text()='{{arg2}}']" parameterized="true"/> + <element name="website" type="checkbox" selector="//*[contains(text(), '{{arg3}}')]" parameterized="true"/> + <element name="save" type="button" selector="//button[@title='Save Role']"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml new file mode 100644 index 0000000000000..98d748b5a30ea --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCreateUserSection"> + <element name="system" type="input" selector="#menu-magento-backend-system"/> + <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> + <element name="create" type="input" selector="#add"/> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="firstNameTextField" type="input" selector="#user_firstname"/> + <element name="lastNameTextField" type="input" selector="#user_lastname"/> + <element name="emailTextField" type="input" selector="#user_email"/> + <element name="passwordTextField" type="input" selector="#user_password"/> + <element name="pwConfirmationTextField" type="input" selector="#user_confirmation"/> + <element name="currentPasswordField" type="input" selector="#user_current_password"/> + <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> + <element name="saveButton" type="button" selector="#save"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml new file mode 100644 index 0000000000000..220c9a444b02f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminDeleteRoleSection"> + <element name="theRole" selector="//td[contains(text(), 'Role')]" type="button"/> + <element name="current_pass" type="button" selector="#current_password"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete Role')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml new file mode 100644 index 0000000000000..bf2e2b44eb602 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminDeleteUserSection"> + <element name="theUser" selector="//td[contains(text(), 'John')]" type="button"/> + <element name="password" selector="#user_current_password" type="input"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete User')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml new file mode 100644 index 0000000000000..e37ce8f4738b3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditRoleInfoSection"> + <element name="roleName" type="input" selector="#role_name"/> + <element name="password" type="input" selector="#current_password"/> + <element name="roleResourcesTab" type="button" selector="#role_info_tabs_account"/> + <element name="backButton" type="button" selector="button[title='Back']"/> + <element name="resetButton" type="button" selector="button[title='Reset']"/> + <element name="deleteButton" type="button" selector="button[title='Delete Role']"/> + <element name="saveButton" type="button" selector="button[title='Save Role']"/> + <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> + <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml new file mode 100644 index 0000000000000..e999413c96d74 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditUserRoleSection"> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml new file mode 100644 index 0000000000000..2e5fcfb7b5c8d --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditUserSection"> + <element name="system" type="input" selector="#menu-magento-backend-system"/> + <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> + <element name="create" type="input" selector="#add"/> + <element name="usernameTextField" type="input" selector="#user_username"/> + <element name="firstNameTextField" type="input" selector="#user_firstname"/> + <element name="lastNameTextField" type="input" selector="#user_lastname"/> + <element name="emailTextField" type="input" selector="#user_email"/> + <element name="passwordTextField" type="input" selector="#user_password"/> + <element name="pwConfirmationTextField" type="input" selector="#user_confirmation"/> + <element name="currentPasswordField" type="input" selector="#user_current_password"/> + <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> + <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + <element name="saveButton" type="button" selector="#save"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml new file mode 100644 index 0000000000000..660c7393b4061 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminMenuSection"> + <element name="dashboard" type="button" selector="//li[@id='menu-magento-backend-dashboard']"/> + <element name="sales" type="button" selector="//li[@id='menu-magento-sales-sales']"/> + <element name="catalog" type="button" selector="//li[@id='menu-magento-catalog-catalog']"/> + <element name="customers" type="button" selector="//li[@id='menu-magento-customer-customer']"/> + <element name="marketing" type="button" selector="//li[@id='//li[@id='menu-magento-backend-marketing']']"/> + <element name="content" type="button" selector="//li[@id='menu-magento-backend-content']"/> + <element name="reports" type="button" selector="//li[@id='menu-magento-reports-report']"/> + <element name="stores" type="button" selector="//li[@id='menu-magento-backend-stores']"/> + <element name="system" type="button" selector="//li[@id='menu-magento-backend-system']"/> + <element name="findPartners" type="button" selector="//li[@id='menu-magento-marketplace-partners']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml new file mode 100644 index 0000000000000..63cbadc71d3d3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminRoleGridSection"> + <element name="idFilterTextField" type="input" selector="#roleGrid_filter_role_id"/> + <element name="roleNameFilterTextField" type="input" selector="#roleGrid_filter_role_name"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml new file mode 100644 index 0000000000000..9564bc61f799c --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminUserGridSection"> + <element name="usernameFilterTextField" type="input" selector="#permissionsUserGrid_filter_username"/> + <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> + <element name="resetButton" type="button" selector="button[title='Reset Filter']"/> + <element name="usernameInFirstRow" type="text" selector=".col-username"/> + <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> + <element name="successMessage" type="text" selector=".message-success"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml new file mode 100644 index 0000000000000..016af2e102744 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="BraintreeConfiguraionSection"> + <element name="titleForBraintreeSettings" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_title']"/> + <element name="environment" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_environment']"/> + <element name="sandbox" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_environment']/option[text()='Sandbox']"/> + <element name="paymentActionSelect" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_payment_action']"/> + <element name="paymentAction" type="button" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_payment_action']/option[text()='Authorize']"/> + <element name="merchantID" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_merchant_id']"/> + <element name="publicKey" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_public_key']"/> + <element name="privateKey" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_private_key']"/> + <element name="enableThisSolution" type="select" selector="//select[@id='payment_us_braintree_section_braintree_active']"/> + <element name="yesForEnable" type="button" selector="//select[@id='payment_us_braintree_section_braintree_active']/option[text()='Yes']"/> + <element name="payPalThroughBraintree" type="select" selector="//select[@id='payment_us_braintree_section_braintree_active_braintree_paypal']"/> + <element name="yesForPayPalThroughBraintree" type="input" selector="//select[@id='payment_us_braintree_section_braintree_active_braintree_paypal']/option[text()='Yes']"/> + <element name="advancedBraintreeSettings" type="button" selector="//a[@id='payment_us_braintree_section_braintree_braintree_advanced-head']"/> + <element name="merchantAccountID" type="text" selector="//input[@id='payment_us_braintree_section_braintree_braintree_advanced_merchant_account_id']"/> + <element name="CVVVerification" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_advanced_useccv']"/> + <element name="yesForCVV" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_advanced_useccv']/option[text()='Yes']"/> + <element name="payPalThroughBraintreeSelector" type="text" selector="//a[@id='payment_us_braintree_section_braintree_braintree_paypal-head']"/> + <element name="titleForPayPalThroughBraintree" type="text" selector="//input[@id='payment_us_braintree_section_braintree_braintree_paypal_title']"/> + <element name="paymentActionInPayPal" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_paypal_payment_action']"/> + <element name="actionAuthorize" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_paypal_payment_action']/option[text()='Authorize']"/> + <element name="save" type="button" selector="//span[text()='Save Config']"/> + <element name="successfulMessage" type="text" selector="//*[@data-ui-id='messages-message-success']"/> + + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml new file mode 100644 index 0000000000000..32f02a69f817e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CatalogSubmenuSection"> + <element name="products" type="button" selector="//li[@id='menu-magento-catalog-catalog']//li[@data-ui-id='menu-magento-catalog-catalog-products']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml new file mode 100644 index 0000000000000..100407438eaae --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ConfigurationListSection"> + <element name="sales" type="button" selector="//div[contains(@class, 'admin__page-nav-title title _collapsible')]/strong[text()='Sales']"/> + <element name="salesPaymentMethods" type="button" selector="//span[text()='Payment Methods']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml new file mode 100644 index 0000000000000..885a45be721f1 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ConfigurationPaymentSection"> + <element name="configureButton" type="button" selector="//button[@id='payment_us_braintree_section_braintree-head']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml new file mode 100644 index 0000000000000..e4a75b1b6a842 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CustomersPageSection"> + <element name="addNewCustomerButton" type="button" selector="//*[@id='add']"/> + <element name="customerCheckbox" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> + <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> + <element name="delete" type="button" selector="//*[contains(@class,'admin__data-grid-header-row row row-gutter')]//*[text()='Delete']"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + <element name="deletedSuccessMessage" type="button" selector="//*[@class='message message-success success']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml new file mode 100644 index 0000000000000..937afb83da96f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CustomersSubmenuSection"> + <element name="allCustomers" type="button" selector="//li[@id='menu-magento-customer-customer']//li[@data-ui-id='menu-magento-customer-customer-manage']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml new file mode 100644 index 0000000000000..d302f9c7d0cba --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewCustomerPageSection"> + <element name="associateToWebsite" type="select" selector="//*[@class='admin__field-control _with-tooltip']//*[@class='admin__control-select']"/> + <element name="group" type="select" selector="//div[@class='admin__field-control admin__control-fields required']//div[@class='admin__field-control']//select[@class='admin__control-select']"/> + <element name="firstName" type="input" selector="//input[@name='customer[firstname]']"/> + <element name="lastName" type="input" selector="//input[@name='customer[lastname]']"/> + <element name="email" type="input" selector="//input[@name='customer[email]']"/> + <element name="addresses" type="button" selector="//a[@id='tab_address']"/> + <element name="addNewAddress" type="button" selector="//span[text()='Add New Addresses']"/> + <element name="defaultBillingAddress" type="button" selector="//label[text()='Default Billing Address']"/> + <element name="defaultShippingAddress" type="button" selector="//label[text()='Default Shipping Address']"/> + <element name="firstNameForAddress" type="button" selector="//input[contains(@name, 'address')][contains(@name, 'firstname')]"/> + <element name="lastNameForAddress" type="button" selector="//input[contains(@name, 'address')][contains(@name, 'lastname')]"/> + <element name="streetAddress" type="button" selector="//input[contains(@name, 'street')]"/> + <element name="city" type="input" selector="//input[contains(@name, 'city')]"/> + <element name="country" type="select" selector="//select[contains(@name, 'country_id')]"/> + <element name="countryArmenia" type="select" selector="//select[contains(@name, 'country_id')]//option[@data-title='Armenia']"/> + <element name="zip" type="input" selector="//input[contains(@name, 'postcode')]"/> + <element name="phoneNumber" type="input" selector="//input[contains(@name, 'telephone')]"/> + <element name="saveCustomer" type="button" selector="//button[@title='Save Customer']"/> + <element name="createdSuccessMessage" type="button" selector="//div[@data-ui-id='messages-message-success']"/> + + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml new file mode 100644 index 0000000000000..13f59ad2cf18e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewOrderSection"> + <element name="createNewOrder" type="button" selector="#add"/> + <element name="customer" type="button" selector="//td[contains(text(), 'Abgar')]"/> + <element name="addProducts" type="button" selector="#add_products"/> + <element name="chooseProduct" type="button" selector="//td[contains(text(),'ProductTest')]"/> + <element name="addSelectedProduct" type="button" selector="//button[@title='Add Selected Product(s) to Order']"/> + <element name="openAddresses" type="button" selector="#order-billing_address_customer_address_id"/> + <element name="chooseAddress" type="button" selector="//select[@id='order-billing_address_customer_address_id']//option[contains(text(), 'Abgar')]"/> + <element name="state" type="button" selector="#order-billing_address_region"/> + <element name="openShippingMethods" type="button" selector="//a[@class='action-default']"/> + <element name="shippingMethod" type="button" selector="//label[@class='admin__field-label' and @for='s_method_flatrate_flatrate']"/> + <element name="creditCardBraintree" type="button" selector="#p_method_braintree"/> + <element name="openCardTypes" type="button" selector="#braintree_cc_type"/> + <element name="masterCard" type="button" selector="//option[contains(text(), 'MasterCard')]"/> + <element name="cardFrame" type="iframe" selector="braintree-hosted-field-number"/> + <element name="monthFrame" type="iframe" selector="braintree-hosted-field-expirationMonth"/> + <element name="yearFrame" type="iframe" selector="braintree-hosted-field-expirationYear"/> + <element name="cvvFrame" type="iframe" selector="braintree-hosted-field-cvv"/> + <element name="creditCardNumber" type="input" selector="#credit-card-number"/> + <element name="expirationMonth" type="input" selector="#expiration-month"/> + <element name="expirationYear" type="input" selector="#expiration-year"/> + <element name="cvv" type="input" selector="#cvv"/> + <element name="submitOrder" type="input" selector="#submit_order_top_button"/> + <element name="successMessage" type="input" selector="#messages"/> + + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml new file mode 100644 index 0000000000000..42e451940c91b --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="NewProductPageSection"> + <element name="productName" type="input" selector="//input[@name='product[name]']"/> + <element name="sku" type="input" selector="//input[@name='product[sku]']"/> + <element name="price" type="input" selector="//input[@name='product[price]']"/> + <element name="quantity" type="input" selector="//input[@name='product[quantity_and_stock_status][qty]']"/> + <element name="saveButton" type="button" selector="//button[@id='save-button']"/> + <element name="createdSuccessMessage" type="button" selector="//div[@data-ui-id='messages-message-success']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml new file mode 100644 index 0000000000000..267efdf3d0e5e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="ProductsPageSection"> + <element name="addProductButton" type="button" selector="//button[@id='add_new_product-button']"/> + <element name="checkboxForProduct" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> + <element name="actions" type="button" selector="//div[@class='col-xs-2']/div[@class='action-select-wrap']/button[@class='action-select']"/> + <element name="delete" type="button" selector="//*[contains(@class,'admin__data-grid-header-row row row-gutter')]//*[text()='Delete']"/> + <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + <element name="deletedSuccessMessage" type="button" selector="//*[@class='message message-success success']"/> + + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml new file mode 100644 index 0000000000000..f094baa9f3446 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="StoresSubmenuSection"> + <element name="configuration" type="button" selector="//li[@id='menu-magento-backend-stores']//li[@data-ui-id='menu-magento-config-system-config']"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml new file mode 100644 index 0000000000000..3a07cbc6dd145 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + + <section name="LoginFormSection"> + <element name="username" type="input" selector="#username"/> + <element name="password" type="input" selector="#login"/> + <element name="signIn" type="button" selector=".actions .action-primary" timeout="30"/> + </section> + + <section name="SignOutSection"> + <element name="admin" type="button" selector=".admin__action-dropdown-text"/> + <element name="logout" type="button" selector="//*[contains(text(), 'Sign Out')]"/> + </section> + +</sections> + diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml new file mode 100644 index 0000000000000..df2e98816f0d3 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CreateAnAdminOrderUsingBraintreePaymentTest1"> + <annotations> + <features value="Backend"/> + <stories value="Creation an admin order using Braintree payment"/> + <title value="Create order using Braintree payment"/> + <description value="Admin should be able to create order using Braintree payment"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-93677"/> + <group value="braintree"/> + <skip> + <issueId value="MQE-1187" /> + </skip> + </annotations> + + + <before> + <!--Login As Admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--CreateNewProduct--> + <actionGroup ref="CreateNewProductActionGroup" stepKey="CreateNewProduct"/> + + <!--Create New Customer--> + <actionGroup ref="CreateCustomerActionGroup" stepKey="CreateCustomer"/> + + </before> + + + <!--Configure Braintree--> + <actionGroup ref="ConfigureBraintree" stepKey="configureBraintree"/> + + <!--Create New Role--> + <actionGroup ref="GoToUserRoles" stepKey="GoToUserRoles"/> + <actionGroup ref="AdminCreateRole" stepKey="AdminCreateNewRole"/> + + <!--Create New User With Specific Role--> + <actionGroup ref="GoToAllUsers" stepKey="GoToAllUsers"/> + <actionGroup ref="AdminCreateUserAction" stepKey="AdminCreateNewUser"/> + + <!--SignOut--> + <actionGroup ref="SignOut" stepKey="signOutFromAdmin"/> + + <!--SignIn New User--> + <actionGroup ref="LoginNewUser" stepKey="signInNewUser"/> + <waitForPageLoad stepKey="waitForLogin" time="3"/> + + <!--Create New Order--> + <actionGroup ref="CreateNewOrderActionGroup" stepKey="createNewOrder"/> + + + <after> + <!--SignOut--> + <actionGroup ref="SignOut" stepKey="signOutFromNewUser"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Delete Product--> + <actionGroup ref="DeleteProductActionGroup" stepKey="DeleteAllProducts"> + <argument name="productName" value="NewProductData.ProductName"/> + </actionGroup> + + <!--Delete Customer--> + <actionGroup ref="DeleteCustomerActionGroup" stepKey="DeleteCustomer"> + <argument name="lastName" value="NewCustomerData.LastName"/> + </actionGroup> + + <!--Delete User --> + <actionGroup ref="GoToAllUsers" stepKey="GoBackToAllUsers"/> + <actionGroup ref="AdminDeleteUserActionGroup" stepKey="AdminDeleteUserActionGroup"/> + + <!--Delete Role--> + <actionGroup ref="GoToUserRoles" stepKey="GoBackToUserRoles"/> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="AdminDeleteRoleActionGroup"/> + + </after> + + </test> +</tests> diff --git a/app/code/Magento/Braintree/Test/Mftf/composer.json b/app/code/Magento/Braintree/Test/Mftf/composer.json deleted file mode 100644 index 2f827424cb7f1..0000000000000 --- a/app/code/Magento/Braintree/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-braintree", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-instant-purchase": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-paypal": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout-agreements": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php index 7b9d59a5bc482..36ea3aea465dd 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Config/ConfigTest.php @@ -142,7 +142,7 @@ public function testGetCcTypesMapper($value, $expected) static::assertEquals( $expected, - $this->model->getCctypesMapper() + $this->model->getCcTypesMapper() ); } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php index 76ab8b8b53b3f..5620e8ffa92b8 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php @@ -5,14 +5,14 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Request; -use Magento\Braintree\Gateway\Config\Config; -use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\PaymentDataBuilder; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Observer\DataAssignObserver; use Magento\Payment\Gateway\Data\OrderAdapterInterface; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Model\Order\Payment; use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Braintree\Gateway\Config\Config; /** * Tests \Magento\Braintree\Gateway\Request\PaymentDataBuilder. @@ -20,18 +20,12 @@ class PaymentDataBuilderTest extends \PHPUnit\Framework\TestCase { const PAYMENT_METHOD_NONCE = 'nonce'; - const MERCHANT_ACCOUNT_ID = '245345'; /** * @var PaymentDataBuilder */ private $builder; - /** - * @var Config|MockObject - */ - private $configMock; - /** * @var Payment|MockObject */ @@ -52,12 +46,12 @@ class PaymentDataBuilderTest extends \PHPUnit\Framework\TestCase */ private $orderMock; + /** + * @inheritdoc + */ protected function setUp() { $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); - $this->configMock = $this->getMockBuilder(Config::class) - ->disableOriginalConstructor() - ->getMock(); $this->paymentMock = $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() ->getMock(); @@ -66,13 +60,19 @@ protected function setUp() ->getMock(); $this->orderMock = $this->createMock(OrderAdapterInterface::class); - $this->builder = new PaymentDataBuilder($this->configMock, $this->subjectReaderMock); + /** @var Config $config */ + $config = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->builder = new PaymentDataBuilder($config, $this->subjectReaderMock); } /** + * @return void * @expectedException \InvalidArgumentException */ - public function testBuildReadPaymentException() + public function testBuildReadPaymentException(): void { $buildSubject = []; @@ -85,9 +85,10 @@ public function testBuildReadPaymentException() } /** + * @return void * @expectedException \InvalidArgumentException */ - public function testBuildReadAmountException() + public function testBuildReadAmountException(): void { $buildSubject = [ 'payment' => $this->paymentDOMock, @@ -106,7 +107,10 @@ public function testBuildReadAmountException() $this->builder->build($buildSubject); } - public function testBuild() + /** + * @return void + */ + public function testBuild(): void { $additionalData = [ [ @@ -118,8 +122,7 @@ public function testBuild() $expectedResult = [ PaymentDataBuilder::AMOUNT => 10.00, PaymentDataBuilder::PAYMENT_METHOD_NONCE => self::PAYMENT_METHOD_NONCE, - PaymentDataBuilder::ORDER_ID => '000000101', - PaymentDataBuilder::MERCHANT_ACCOUNT_ID => self::MERCHANT_ACCOUNT_ID, + PaymentDataBuilder::ORDER_ID => '000000101' ]; $buildSubject = [ @@ -131,10 +134,6 @@ public function testBuild() ->method('getAdditionalInformation') ->willReturnMap($additionalData); - $this->configMock->expects(self::once()) - ->method('getMerchantAccountId') - ->willReturn(self::MERCHANT_ACCOUNT_ID); - $this->paymentDOMock->expects(self::once()) ->method('getPayment') ->willReturn($this->paymentMock); diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php new file mode 100644 index 0000000000000..2248aab1aad2e --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/AvailabilityCheckerTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\CreditCard; + +use Magento\Braintree\Gateway\Config\Config; +use Magento\Braintree\Model\InstantPurchase\CreditCard\AvailabilityChecker; + +/** + * @covers \Magento\Braintree\Model\InstantPurchase\CreditCard\AvailabilityChecker + */ +class AvailabilityCheckerTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var AvailabilityChecker + */ + private $availabilityChecker; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->availabilityChecker = new AvailabilityChecker($this->configMock); + } + + /** + * Test isAvailable method + * + * @dataProvider isAvailableDataProvider + * + * @param bool $isVerify3DSecure + * @param bool $expected + * + * @return void + */ + public function testIsAvailable(bool $isVerify3DSecure, bool $expected) + { + $this->configMock->expects($this->once())->method('isVerify3DSecure')->willReturn($isVerify3DSecure); + $actual = $this->availabilityChecker->isAvailable(); + self::assertEquals($expected, $actual); + } + + /** + * Data provider for isAvailable method test + * + * @return array + */ + public function isAvailableDataProvider() + { + return [ + [true, false], + [false, true], + ]; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php new file mode 100644 index 0000000000000..a5c7cd743d85f --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/CreditCard/TokenFormatterTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\CreditCard; + +use Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter as CreditCardTokenFormatter; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +class TokenFormatterTest extends TestCase +{ + /** + * @var PaymentTokenInterface|PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var CreditCardTokenFormatter + */ + private $creditCardTokenFormatter; + + /** + * @var array + */ + private $tokenDetails = [ + 'type' => 'visa', + 'maskedCC' => '1111************9999', + 'expirationDate' => '01-01-2020' + ]; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->paymentTokenMock = $this->getMockBuilder(PaymentTokenInterface::class) + ->getMockForAbstractClass(); + + $this->creditCardTokenFormatter = new CreditCardTokenFormatter(); + } + + /** + * Testing the payment format with a known credit card type + * + * @return void + */ + public function testFormatPaymentTokenWithKnownCardType() + { + $this->tokenDetails['type'] = key(CreditCardTokenFormatter::$baseCardTypes); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + reset(CreditCardTokenFormatter::$baseCardTypes), + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals( + $formattedString, + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock) + ); + } + + /** + * Testing the payment format with a unknown credit card type + * + * @return void + */ + public function testFormatPaymentTokenWithUnknownCardType() + { + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + $this->tokenDetails['type'], + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals( + $formattedString, + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock) + ); + } + + /** + * Testing the payment format with wrong card data + * + * @return void + */ + public function testFormatPaymentTokenWithWrongData() + { + unset($this->tokenDetails['type']); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + self::expectException('\InvalidArgumentException'); + + $this->creditCardTokenFormatter->formatPaymentToken($this->paymentTokenMock); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php new file mode 100644 index 0000000000000..e4cd8fd58043b --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PayPal/TokenFormatterTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase\PayPal; + +use Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter as PaypalTokenFormatter; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +class TokenFormatterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var PaymentTokenInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var PaypalTokenFormatter + */ + private $paypalTokenFormatter; + + /** + * @var array + */ + private $tokenDetails = [ + 'type' => 'visa', + 'maskedCC' => '4444************9999', + 'expirationDate' => '07-07-2025' + ]; + + /** + * Test setup + */ + protected function setUp() + { + $this->paymentTokenMock = $this->getMockBuilder(PaymentTokenInterface::class) + ->getMockForAbstractClass(); + + $this->paypalTokenFormatter = new PaypalTokenFormatter(); + } + + /** + * testFormatPaymentTokenWithKnownCardType + */ + public function testFormatPaymentTokenWithKnownCardType() + { + $this->tokenDetails['type'] = key(PaypalTokenFormatter::$baseCardTypes); + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + reset(PaypalTokenFormatter::$baseCardTypes), + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals($formattedString, $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock)); + } + + /** + * testFormatPaymentTokenWithUnknownCardType + */ + public function testFormatPaymentTokenWithUnknownCardType() + { + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + + $formattedString = sprintf( + '%s: %s, %s: %s (%s: %s)', + __('Credit Card'), + $this->tokenDetails['type'], + __('ending'), + $this->tokenDetails['maskedCC'], + __('expires'), + $this->tokenDetails['expirationDate'] + ); + + self::assertEquals($formattedString, $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock)); + } + + /** + * testFormatPaymentTokenWithWrongData + */ + public function testFormatPaymentTokenWithWrongData() + { + unset($this->tokenDetails['type']); + + $this->paymentTokenMock->expects($this->once()) + ->method('getTokenDetails') + ->willReturn(json_encode($this->tokenDetails)); + self::expectException('\InvalidArgumentException'); + + $this->paypalTokenFormatter->formatPaymentToken($this->paymentTokenMock); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php new file mode 100644 index 0000000000000..2631fcbe5f5b5 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/InstantPurchase/PaymentAdditionalInformationProviderTest.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model\InstantPurchase; + +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider; +use Magento\Payment\Gateway\Command\Result\ArrayResult; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +/** + * @covers \Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider + */ +class PaymentAdditionalInformationProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var PaymentAdditionalInformationProvider + */ + private $paymentAdditionalInformationProvider; + + /** + * @var GetPaymentNonceCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $getPaymentNonceCommandMock; + + /** + * @var PaymentTokenInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentTokenMock; + + /** + * @var ArrayResult|\PHPUnit_Framework_MockObject_MockObject + */ + private $arrayResultMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->getPaymentNonceCommandMock = $this->createMock(GetPaymentNonceCommand::class); + $this->paymentTokenMock = $this->createMock(PaymentTokenInterface::class); + $this->arrayResultMock = $this->createMock(ArrayResult::class); + $this->paymentAdditionalInformationProvider = new PaymentAdditionalInformationProvider( + $this->getPaymentNonceCommandMock + ); + } + + /** + * Test getAdditionalInformation method + * + * @return void + */ + public function testGetAdditionalInformation() + { + $customerId = 15; + $publicHash = '3n4b7sn48g'; + $paymentMethodNonce = 'test'; + + $this->paymentTokenMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->paymentTokenMock->expects($this->once())->method('getPublicHash')->willReturn($publicHash); + $this->getPaymentNonceCommandMock->expects($this->once())->method('execute')->with([ + PaymentTokenInterface::CUSTOMER_ID => $customerId, + PaymentTokenInterface::PUBLIC_HASH => $publicHash, + ])->willReturn($this->arrayResultMock); + $this->arrayResultMock->expects($this->once())->method('get') + ->willReturn(['paymentMethodNonce' => $paymentMethodNonce]); + + $expected = [ + 'payment_method_nonce' => $paymentMethodNonce, + ]; + $actual = $this->paymentAdditionalInformationProvider->getAdditionalInformation($this->paymentTokenMock); + self::assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php new file mode 100644 index 0000000000000..b6ef534c55c29 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/LocaleResolverTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Test\Unit\Model; + +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\LocaleResolver; +use Magento\Framework\Locale\ResolverInterface; + +/** + * @covers \Magento\Braintree\Model\LocaleResolver + */ +class LocaleResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var LocaleResolver + */ + private $localeResolver; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var ResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $resolverMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->resolverMock = $this->createMock(ResolverInterface::class); + $this->localeResolver = new LocaleResolver($this->resolverMock, $this->configMock); + } + + /** + * Test getDefaultLocalePath method + * + * @return void + */ + public function testGetDefaultLocalePath() + { + $expected = 'general/locale/code'; + $this->resolverMock->expects($this->once())->method('getDefaultLocalePath')->willReturn($expected); + $actual = $this->localeResolver->getDefaultLocalePath(); + self::assertEquals($expected, $actual); + } + + /** + * Test setDefaultLocale method + * + * @return void + */ + public function testSetDefaultLocale() + { + $defaultLocale = 'en_US'; + $this->resolverMock->expects($this->once())->method('setDefaultLocale')->with($defaultLocale); + $this->localeResolver->setDefaultLocale($defaultLocale); + } + + /** + * Test getDefaultLocale method + * + * @return void + */ + public function testGetDefaultLocale() + { + $expected = 'fr_FR'; + $this->resolverMock->expects($this->once())->method('getDefaultLocale')->willReturn($expected); + $actual = $this->localeResolver->getDefaultLocale(); + self::assertEquals($expected, $actual); + } + + /** + * Test setLocale method + * + * @return void + */ + public function testSetLocale() + { + $locale = 'en_GB'; + $this->resolverMock->expects($this->once())->method('setLocale')->with($locale); + $this->localeResolver->setLocale($locale); + } + + /** + * Test getLocale method + * + * @return void + */ + public function testGetLocale() + { + $locale = 'en_TEST'; + $allowedLocales = 'en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL'; + $this->resolverMock->expects($this->once())->method('getLocale')->willReturn($locale); + $this->configMock->expects($this->once())->method('getValue')->with('supported_locales') + ->willReturn($allowedLocales); + + $expected = 'en_US'; + $actual = $this->localeResolver->getLocale(); + self::assertEquals($expected, $actual); + } + + /** + * Test emulate method + * + * @return void + */ + public function testEmulate() + { + $scopeId = 12; + $this->resolverMock->expects($this->once())->method('emulate')->with($scopeId); + $this->localeResolver->emulate($scopeId); + } + + /** + * Test revert method + * + * @return void + */ + public function testRevert() + { + $this->resolverMock->expects($this->once())->method('revert'); + $this->localeResolver->revert(); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php index 76bf5b659bda3..62452228b6186 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php @@ -51,6 +51,9 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase */ private $quoteUpdater; + /** + * @return void + */ protected function setUp() { $this->configMock = $this->getMockBuilder(Config::class) @@ -99,6 +102,10 @@ protected function setUp() ); } + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecute() { $details = $this->getDetails(); @@ -121,6 +128,9 @@ public function testExecute() $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quoteMock); } + /** + * @return void + */ private function disabledQuoteAddressValidationStep() { $this->billingAddressMock->expects(self::once()) @@ -300,7 +310,7 @@ private function getQuoteMock() $cartExtensionMock = $this->getMockBuilder(CartExtensionInterface::class) ->setMethods(['setShippingAssignments']) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $quoteMock->expects(self::any()) ->method('getExtensionAttributes') diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 8fb98dce72c92..ba51a5436fedb 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -29,7 +29,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Braintree/etc/acl.xml b/app/code/Magento/Braintree/etc/acl.xml index a188586cc0a26..066ccc818d4e6 100644 --- a/app/code/Magento/Braintree/etc/acl.xml +++ b/app/code/Magento/Braintree/etc/acl.xml @@ -11,7 +11,16 @@ <resource id="Magento_Backend::admin"> <resource id="Magento_Reports::report"> <resource id="Magento_Reports::salesroot"> - <resource id="Magento_Braintree::settlement_report" title="Braintree Settlement" sortOrder="80" /> + <resource id="Magento_Braintree::settlement_report" title="Braintree Settlement" translate="title" sortOrder="80" /> + </resource> + </resource> + <resource id="Magento_Sales::sales"> + <resource id="Magento_Sales::sales_operation"> + <resource id="Magento_Sales::sales_order"> + <resource id="Magento_Sales::actions"> + <resource id="Magento_Braintree::get_client_token" title="Get Client Token Braintree" sortOrder="170" /> + </resource> + </resource> </resource> </resource> </resource> diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index 7a803f803ae89..9de1ad48d2261 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -29,6 +29,7 @@ <item name="vault" xsi:type="string">Magento\Braintree\Gateway\Request\VaultDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -41,6 +42,7 @@ <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/Braintree/etc/adminhtml/menu.xml b/app/code/Magento/Braintree/etc/adminhtml/menu.xml index 590d5b3dce008..ce4dd4844f3bc 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/menu.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/menu.xml @@ -10,6 +10,7 @@ <add id="Magento_Braintree::settlement_report" title="Braintree Settlement" + translate="title" module="Magento_Braintree" sortOrder="80" parent="Magento_Reports::report_salesroot" diff --git a/app/code/Magento/Braintree/etc/adminhtml/system.xml b/app/code/Magento/Braintree/etc/adminhtml/system.xml index c49402070f0fd..5215dbc00b7ef 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/system.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/system.xml @@ -84,18 +84,18 @@ <label>Vault Title</label> <config_path>payment/braintree_cc_vault/title</config_path> </field> - <field id="merchant_account_id" translate="label" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="merchant_account_id" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Merchant Account ID</label> <comment>If you don't specify the merchant account to use to process a transaction, Braintree will process it using your default merchant account.</comment> <config_path>payment/braintree/merchant_account_id</config_path> </field> - <field id="fraudprotection" translate="label" type="select" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="fraudprotection" translate="label comment" type="select" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Advanced Fraud Protection</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable Advanced Fraud Protection in Your Braintree Account in Settings/Processing Section</comment> <config_path>payment/braintree/fraudprotection</config_path> </field> - <field id="kount_id" translate="label" sortOrder="35" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="kount_id" translate="label comment" sortOrder="35" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Kount Merchant ID</label> <comment><![CDATA[Used for direct fraud tool integration. Make sure you also contact <a href="mailto:accounts@braintreepayments.com">accounts@braintreepayments.com</a> to setup your Kount account.]]></comment> <depends> @@ -108,7 +108,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree/debug</config_path> </field> - <field id="useccv" translate="label" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="useccv" translate="label comment" type="select" sortOrder="150" showInDefault="1" showInWebsite="1" showInStore="0"> <label>CVV Verification</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Be sure to Enable AVS and/or CVV in Your Braintree Account in Settings/Processing Section.</comment> @@ -149,7 +149,7 @@ <group id="braintree_paypal" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> <label>PayPal through Braintree</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="title" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Title</label> <config_path>payment/braintree_paypal/title</config_path> <comment>It is recommended to set this value to "PayPal" per store views.</comment> @@ -187,7 +187,7 @@ <can_be_empty>1</can_be_empty> <config_path>payment/braintree_paypal/specificcountry</config_path> </field> - <field id="require_billing_address" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="require_billing_address" translate="label comment" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Require Customer's Billing Address</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/require_billing_address</config_path> @@ -203,7 +203,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/debug</config_path> </field> - <field id="display_on_shopping_cart" translate="label" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="display_on_shopping_cart" translate="label comment" type="select" sortOrder="120" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Display on Shopping Cart</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>payment/braintree_paypal/display_on_shopping_cart</config_path> @@ -239,14 +239,14 @@ <config_path>payment/braintree/verify_specific_countries</config_path> </field> </group> - <group id="braintree_dynamic_descriptor" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> + <group id="braintree_dynamic_descriptor" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> <label>Dynamic Descriptors</label> <comment><![CDATA[Dynamic descriptors are sent on a per-transaction basis and define what will appear on your customers credit card statements for a specific purchase. The clearer the description of your product, the less likely customers will issue chargebacks due to confusion or non-recognition. Dynamic descriptors are not enabled on all accounts by default. If you receive a validation error of 92203 or if your dynamic descriptors are not displaying as expected, please <a href="mailto:support@getbraintree.com">Braintree Support</a>.]]></comment> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - <field id="name" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="name" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Name</label> <config_path>payment/braintree/descriptor_name</config_path> <comment> @@ -254,14 +254,14 @@ and the product descriptor can be up to 18, 14, or 9 characters respectively (with an * in between for a total descriptor name of 22 characters). </comment> </field> - <field id="phone" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="phone" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Phone</label> <config_path>payment/braintree/descriptor_phone</config_path> <comment> The value in the phone number field of a customer's statement. Phone must be 10-14 characters and can only contain numbers, dashes, parentheses and periods. </comment> </field> - <field id="url" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="url" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>URL</label> <config_path>payment/braintree/descriptor_url</config_path> <comment> diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 290fb5be58f34..67c90e6991e28 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -233,6 +233,7 @@ <item name="device_data" xsi:type="string">Magento\Braintree\Gateway\Request\KountPaymentDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -291,6 +292,7 @@ <item name="device_data" xsi:type="string">Magento\Braintree\Gateway\Request\KountPaymentDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -325,6 +327,7 @@ <item name="vault_capture" xsi:type="string">Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder</item> <item name="settlement" xsi:type="string">Magento\Braintree\Gateway\Request\SettlementDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -347,6 +350,7 @@ <item name="device_data" xsi:type="string">Magento\Braintree\Gateway\Request\PayPal\DeviceDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> @@ -380,6 +384,7 @@ <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> + <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index 194ad14d49751..6bf677151ed0d 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -190,4 +190,5 @@ Currency,Currency "Partial settlements are not supported by this processor.","Partial settlements are not supported by this processor." "Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.","Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending." "Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later." -"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later." \ No newline at end of file +"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later." +"Braintree Settlement","Braintree Settlement" diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js index 9c95b79196e9d..ab01565d7f1e5 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/braintree.js @@ -24,6 +24,7 @@ define([ scriptLoaded: false, braintree: null, selectedCardType: null, + checkout: null, imports: { onActiveChange: 'active' } @@ -147,6 +148,12 @@ define([ this.disableEventListeners(); + if (self.checkout) { + self.checkout.teardown(function () { + self.checkout = null; + }); + } + self.braintree.setup(self.clientToken, 'custom', { id: self.selector, hostedFields: self.getHostedFields(), @@ -154,7 +161,8 @@ define([ /** * Triggered when sdk was loaded */ - onReady: function () { + onReady: function (checkout) { + self.checkout = checkout; $('body').trigger('processStop'); self.enableEventListeners(); }, diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml index 1d60d19458a28..c1ef461ecae7c 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml @@ -29,6 +29,7 @@ $config = [ class="action-braintree-paypal-logo" disabled> <img class="braintree-paypal-button-hidden" src="https://checkout.paypal.com/pwpp/2.17.6/images/pay-with-paypal.png" - alt="Pay with PayPal"/> + alt="<?= $block->escapeHtml(__('Pay with PayPal')) ?>" + title="<?= $block->escapeHtml(__('Pay with PayPal')) ?>"/> </button> </div> 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 ca9d3686958b4..7c75cc3e594ee 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -7,6 +7,7 @@ define([ 'jquery', 'underscore', + 'mage/utils/wrapper', 'Magento_Checkout/js/view/payment/default', 'Magento_Braintree/js/view/payment/adapter', 'Magento_Checkout/js/model/quote', @@ -18,6 +19,7 @@ define([ ], function ( $, _, + wrapper, Component, Braintree, quote, @@ -218,8 +220,9 @@ define([ /** * Re-init PayPal Auth Flow + * @param {Function} callback - Optional callback */ - reInitPayPal: function () { + reInitPayPal: function (callback) { if (Braintree.checkout) { Braintree.checkout.teardown(function () { Braintree.checkout = null; @@ -228,6 +231,18 @@ define([ this.disableButton(); this.clientConfig.paypal.amount = this.grandTotalAmount; + this.clientConfig.paypal.shippingAddressOverride = this.getShippingAddress(); + + if (callback) { + this.clientConfig.onReady = wrapper.wrap( + this.clientConfig.onReady, + function (original, checkout) { + this.clientConfig.onReady = original; + original(checkout); + callback(); + }.bind(this) + ); + } Braintree.setConfig(this.clientConfig); Braintree.setup(); @@ -403,7 +418,11 @@ define([ * Triggers when customer click "Continue to PayPal" button */ payWithPayPal: function () { - if (additionalValidators.validate()) { + this.reInitPayPal(function () { + if (!additionalValidators.validate()) { + return; + } + try { Braintree.checkout.paypal.initAuthFlow(); } catch (e) { @@ -411,7 +430,7 @@ define([ message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.') }); } - } + }.bind(this)); }, /** diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html index f5c8c15c8f3ba..e1f6a1b4c25ce 100644 --- a/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/paypal.html @@ -12,7 +12,7 @@ data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()" /> <label class="label" data-bind="attr: {'for': getCode()}"> <!-- PayPal Logo --> - <img data-bind="attr: {src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark')}" + <img data-bind="attr: {src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark'), title: $t('Acceptance Mark')}" class="payment-icon"/> <!-- PayPal Logo --> <span text="getTitle()"></span> @@ -45,7 +45,7 @@ </span> <div class="field-tooltip-content" data-target="dropdown" - translate="'We store you payment information securely on Braintree servers via SSL.'"></div> + translate="'We store your payment information securely on Braintree servers via SSL.'"></div> </div> </div> <!-- /ko --> diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php index 0b6e97cfb9299..b5dfd312cd0c4 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Indexer/Price.php @@ -6,20 +6,171 @@ namespace Magento\Bundle\Model\ResourceModel\Indexer; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query\JoinAttributeProcessor; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Catalog\Model\Product\Attribute\Source\Status; /** * Bundle products Price indexer resource model * - * @author Magento Core Team <core@magentocommerce.com> + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Price extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice +class Price implements DimensionalIndexerInterface { /** - * @inheritdoc + * @var IndexTableStructureFactory */ - protected function reindex($entityIds = null) + private $indexTableStructureFactory; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var bool + */ + private $fullReindexAction; + + /** + * @var string + */ + private $connectionName; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * Mapping between dimensions and field in database + * + * @var array + */ + private $dimensionToFieldMapper = [ + WebsiteDimensionProvider::DIMENSION_NAME => 'pw.website_id', + CustomerGroupDimensionProvider::DIMENSION_NAME => 'cg.customer_group_id', + ]; + + /** + * @var BasePriceModifier + */ + private $basePriceModifier; + + /** + * @var JoinAttributeProcessor + */ + private $joinAttributeProcessor; + + /** + * @var \Magento\Framework\Event\ManagerInterface + */ + private $eventManager; + + /** + * @var \Magento\Framework\Module\Manager + */ + private $moduleManager; + + /** + * @param IndexTableStructureFactory $indexTableStructureFactory + * @param TableMaintainer $tableMaintainer + * @param MetadataPool $metadataPool + * @param \Magento\Framework\App\ResourceConnection $resource + * @param BasePriceModifier $basePriceModifier + * @param JoinAttributeProcessor $joinAttributeProcessor + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\Module\Manager $moduleManager + * @param bool $fullReindexAction + * @param string $connectionName + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + IndexTableStructureFactory $indexTableStructureFactory, + TableMaintainer $tableMaintainer, + MetadataPool $metadataPool, + \Magento\Framework\App\ResourceConnection $resource, + BasePriceModifier $basePriceModifier, + JoinAttributeProcessor $joinAttributeProcessor, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Framework\Module\Manager $moduleManager, + $fullReindexAction = false, + $connectionName = 'indexer' + ) { + $this->indexTableStructureFactory = $indexTableStructureFactory; + $this->tableMaintainer = $tableMaintainer; + $this->connectionName = $connectionName; + $this->metadataPool = $metadataPool; + $this->resource = $resource; + $this->fullReindexAction = $fullReindexAction; + $this->basePriceModifier = $basePriceModifier; + $this->joinAttributeProcessor = $joinAttributeProcessor; + $this->eventManager = $eventManager; + $this->moduleManager = $moduleManager; + } + + /** + * {@inheritdoc} + * @param array $dimensions + * @param \Traversable $entityIds + * @throws \Exception + */ + public function executeByDimensions(array $dimensions, \Traversable $entityIds) { - $this->_prepareBundlePrice($entityIds); + $this->tableMaintainer->createMainTmpTable($dimensions); + + $temporaryPriceTable = $this->indexTableStructureFactory->create([ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ]); + + $entityIds = iterator_to_array($entityIds); + + $this->prepareTierPriceIndex($dimensions, $entityIds); + + $this->prepareBundlePriceTable(); + + $this->prepareBundlePriceByType( + \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, + $dimensions, + $entityIds + ); + + $this->prepareBundlePriceByType( + \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, + $dimensions, + $entityIds + ); + + $this->calculateBundleOptionPrice($temporaryPriceTable, $dimensions); + + $this->basePriceModifier->modifyPrice($temporaryPriceTable, $entityIds); } /** @@ -27,9 +178,9 @@ protected function reindex($entityIds = null) * * @return string */ - protected function _getBundlePriceTable() + private function getBundlePriceTable() { - return $this->tableStrategy->getTableName('catalog_product_index_price_bundle'); + return $this->getTable('catalog_product_index_price_bundle_tmp'); } /** @@ -37,9 +188,9 @@ protected function _getBundlePriceTable() * * @return string */ - protected function _getBundleSelectionTable() + private function getBundleSelectionTable() { - return $this->tableStrategy->getTableName('catalog_product_index_price_bundle_sel'); + return $this->getTable('catalog_product_index_price_bundle_sel_tmp'); } /** @@ -47,9 +198,9 @@ protected function _getBundleSelectionTable() * * @return string */ - protected function _getBundleOptionTable() + private function getBundleOptionTable() { - return $this->tableStrategy->getTableName('catalog_product_index_price_bundle_opt'); + return $this->getTable('catalog_product_index_price_bundle_opt_tmp'); } /** @@ -57,9 +208,9 @@ protected function _getBundleOptionTable() * * @return $this */ - protected function _prepareBundlePriceTable() + private function prepareBundlePriceTable() { - $this->getConnection()->delete($this->_getBundlePriceTable()); + $this->getConnection()->delete($this->getBundlePriceTable()); return $this; } @@ -68,9 +219,9 @@ protected function _prepareBundlePriceTable() * * @return $this */ - protected function _prepareBundleSelectionTable() + private function prepareBundleSelectionTable() { - $this->getConnection()->delete($this->_getBundleSelectionTable()); + $this->getConnection()->delete($this->getBundleSelectionTable()); return $this; } @@ -79,9 +230,9 @@ protected function _prepareBundleSelectionTable() * * @return $this */ - protected function _prepareBundleOptionTable() + private function prepareBundleOptionTable() { - $this->getConnection()->delete($this->_getBundleOptionTable()); + $this->getConnection()->delete($this->getBundleOptionTable()); return $this; } @@ -89,51 +240,58 @@ protected function _prepareBundleOptionTable() * Prepare temporary price index data for bundle products by price type * * @param int $priceType + * @param array $dimensions * @param int|array $entityIds the entity ids limitation - * @return $this + * @return void + * @throws \Exception * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _prepareBundlePriceByType($priceType, $entityIds = null) + private function prepareBundlePriceByType($priceType, array $dimensions, $entityIds = null) { $connection = $this->getConnection(); - $table = $this->_getBundlePriceTable(); - $select = $connection->select()->from( ['e' => $this->getTable('catalog_product_entity')], ['entity_id'] - )->join( + )->joinInner( ['cg' => $this->getTable('customer_group')], - '', + array_key_exists(CustomerGroupDimensionProvider::DIMENSION_NAME, $dimensions) + ? sprintf( + '%s = %s', + $this->dimensionToFieldMapper[CustomerGroupDimensionProvider::DIMENSION_NAME], + $dimensions[CustomerGroupDimensionProvider::DIMENSION_NAME]->getValue() + ) : '', ['customer_group_id'] - ); - $this->_addWebsiteJoinToSelect($select, true); - $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', "e.entity_id"); - $select->columns( - 'website_id', - 'cw' - )->join( - ['cwd' => $this->_getWebsiteDateTable()], - 'cw.website_id = cwd.website_id', + )->joinInner( + ['pw' => $this->getTable('catalog_product_website')], + 'pw.product_id = e.entity_id', + ['pw.website_id'] + )->joinInner( + ['cwd' => $this->getTable('catalog_product_index_website')], + 'pw.website_id = cwd.website_id', [] - )->joinLeft( - ['tp' => $this->_getTierPriceIndexTable()], - 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' . + ); + $select->joinLeft( + ['tp' => $this->getTable('catalog_product_index_tier_price')], + 'tp.entity_id = e.entity_id AND tp.website_id = pw.website_id' . ' AND tp.customer_group_id = cg.customer_group_id', [] )->where( 'e.type_id=?', - $this->getTypeId() + \Magento\Bundle\Ui\DataProvider\Product\Listing\Collector\BundlePrice::PRODUCT_TYPE ); - // add enable products limitation - $statusCond = $connection->quoteInto( - '=?', - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED - ); - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); - $this->_addAttributeToSelect($select, 'status', "e.$linkField", 'cs.store_id', $statusCond, true); + foreach ($dimensions as $dimension) { + if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) { + throw new \LogicException( + 'Provided dimension is not valid for Price indexer: ' . $dimension->getName() + ); + } + $select->where($this->dimensionToFieldMapper[$dimension->getName()] . ' = ?', $dimension->getValue()); + } + + $this->joinAttributeProcessor->process($select, 'status', Status::STATUS_ENABLED); if ($this->moduleManager->isEnabled('Magento_Tax')) { - $taxClassId = $this->_addAttributeToSelect($select, 'tax_class_id', "e.$linkField", 'cs.store_id'); + $taxClassId = $this->joinAttributeProcessor->process($select, 'tax_class_id'); } else { $taxClassId = new \Zend_Db_Expr('0'); } @@ -146,13 +304,12 @@ protected function _prepareBundlePriceByType($priceType, $entityIds = null) ); } - $priceTypeCond = $connection->quoteInto('=?', $priceType); - $this->_addAttributeToSelect($select, 'price_type', "e.$linkField", 'cs.store_id', $priceTypeCond); + $this->joinAttributeProcessor->process($select, 'price_type', $priceType); - $price = $this->_addAttributeToSelect($select, 'price', "e.$linkField", 'cs.store_id'); - $specialPrice = $this->_addAttributeToSelect($select, 'special_price', "e.$linkField", 'cs.store_id'); - $specialFrom = $this->_addAttributeToSelect($select, 'special_from_date', "e.$linkField", 'cs.store_id'); - $specialTo = $this->_addAttributeToSelect($select, 'special_to_date', "e.$linkField", 'cs.store_id'); + $price = $this->joinAttributeProcessor->process($select, 'price'); + $specialPrice = $this->joinAttributeProcessor->process($select, 'special_price'); + $specialFrom = $this->joinAttributeProcessor->process($select, 'special_from_date'); + $specialTo = $this->joinAttributeProcessor->process($select, 'special_to_date'); $currentDate = new \Zend_Db_Expr('cwd.website_date'); $specialFromDate = $connection->getDatePartSql($specialFrom); @@ -205,39 +362,41 @@ protected function _prepareBundlePriceByType($priceType, $entityIds = null) /** * Add additional external limitation */ - $this->_eventManager->dispatch( + $this->eventManager->dispatch( 'catalog_product_prepare_index_select', [ 'select' => $select, 'entity_field' => new \Zend_Db_Expr('e.entity_id'), - 'website_field' => new \Zend_Db_Expr('cw.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'website_field' => new \Zend_Db_Expr('pw.website_id'), + 'store_field' => new \Zend_Db_Expr('cwd.default_store_id') ] ); - $query = $select->insertFromSelect($table); + $query = $select->insertFromSelect($this->getBundlePriceTable()); $connection->query($query); - - return $this; } /** * Calculate fixed bundle product selections price * - * @return $this + * @param IndexTableStructure $priceTable + * @param array $dimensions + * + * @return void + * @throws \Exception */ - protected function _calculateBundleOptionPrice() + private function calculateBundleOptionPrice($priceTable, $dimensions) { $connection = $this->getConnection(); - $this->_prepareBundleSelectionTable(); - $this->_calculateBundleSelectionPrice(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED); - $this->_calculateBundleSelectionPrice(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC); + $this->prepareBundleSelectionTable(); + $this->calculateBundleSelectionPrice($dimensions, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED); + $this->calculateBundleSelectionPrice($dimensions, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC); - $this->_prepareBundleOptionTable(); + $this->prepareBundleOptionTable(); $select = $connection->select()->from( - $this->_getBundleSelectionTable(), + $this->getBundleSelectionTable(), ['entity_id', 'customer_group_id', 'website_id', 'option_id'] )->group( ['entity_id', 'customer_group_id', 'website_id', 'option_id'] @@ -254,24 +413,24 @@ protected function _calculateBundleOptionPrice() ] ); - $query = $select->insertFromSelect($this->_getBundleOptionTable()); + $query = $select->insertFromSelect($this->getBundleOptionTable()); $connection->query($query); - $this->_prepareDefaultFinalPriceTable(); - $this->applyBundlePrice(); - $this->applyBundleOptionPrice(); - - return $this; + $this->getConnection()->delete($priceTable->getTableName()); + $this->applyBundlePrice($priceTable); + $this->applyBundleOptionPrice($priceTable); } /** * Calculate bundle product selections price by product type * + * @param array $dimensions * @param int $priceType - * @return $this + * @return void + * @throws \Exception * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _calculateBundleSelectionPrice($priceType) + private function calculateBundleSelectionPrice($dimensions, $priceType) { $connection = $this->getConnection(); @@ -334,9 +493,10 @@ protected function _calculateBundleSelectionPrice($priceType) ]); } - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); $select = $connection->select()->from( - ['i' => $this->_getBundlePriceTable()], + ['i' => $this->getBundlePriceTable()], ['entity_id', 'customer_group_id', 'website_id'] )->join( ['parent_product' => $this->getTable('catalog_product_entity')], @@ -355,7 +515,7 @@ protected function _calculateBundleSelectionPrice($priceType) 'bs.selection_id = bsp.selection_id AND bsp.website_id = i.website_id', [''] )->join( - ['idx' => $this->getIdxTable()], + ['idx' => $this->getMainTable($dimensions)], 'bs.product_id = idx.entity_id AND i.customer_group_id = idx.customer_group_id' . ' AND i.website_id = idx.website_id', [] @@ -375,49 +535,26 @@ protected function _calculateBundleSelectionPrice($priceType) ] ); - $query = $select->insertFromSelect($this->_getBundleSelectionTable()); + $query = $select->insertFromSelect($this->getBundleSelectionTable()); $connection->query($query); - - return $this; - } - - /** - * Prepare temporary index price for bundle products - * - * @param int|array $entityIds the entity ids limitation - * @return $this - */ - protected function _prepareBundlePrice($entityIds = null) - { - if (!$this->hasEntity() && empty($entityIds)) { - return $this; - } - $this->_prepareTierPriceIndex($entityIds); - $this->_prepareBundlePriceTable(); - $this->_prepareBundlePriceByType(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, $entityIds); - $this->_prepareBundlePriceByType(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, $entityIds); - - $this->_calculateBundleOptionPrice(); - $this->_applyCustomOption(); - - $this->_movePriceDataToIndexTable(); - - return $this; } /** * Prepare percentage tier price for bundle products * - * @param int|array $entityIds - * @return $this + * @param array $dimensions + * @param array $entityIds + * @return void + * @throws \Exception */ - protected function _prepareTierPriceIndex($entityIds = null) + private function prepareTierPriceIndex($dimensions, $entityIds) { $connection = $this->getConnection(); - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); // remove index by bundle products $select = $connection->select()->from( - ['i' => $this->_getTierPriceIndexTable()], + ['i' => $this->getTable('catalog_product_index_tier_price')], null )->join( ['e' => $this->getTable('catalog_product_entity')], @@ -425,7 +562,7 @@ protected function _prepareTierPriceIndex($entityIds = null) [] )->where( 'e.type_id=?', - $this->getTypeId() + \Magento\Bundle\Ui\DataProvider\Product\Listing\Collector\BundlePrice::PRODUCT_TYPE ); $query = $select->deleteFromSelect('i'); $connection->query($query); @@ -442,40 +579,47 @@ protected function _prepareTierPriceIndex($entityIds = null) 'tp.all_groups = 1 OR (tp.all_groups = 0 AND tp.customer_group_id = cg.customer_group_id)', ['customer_group_id'] )->join( - ['cw' => $this->getTable('store_website')], - 'tp.website_id = 0 OR tp.website_id = cw.website_id', + ['pw' => $this->getTable('store_website')], + 'tp.website_id = 0 OR tp.website_id = pw.website_id', ['website_id'] )->where( - 'cw.website_id != 0' + 'pw.website_id != 0' )->where( 'e.type_id=?', - $this->getTypeId() + \Magento\Bundle\Ui\DataProvider\Product\Listing\Collector\BundlePrice::PRODUCT_TYPE )->columns( new \Zend_Db_Expr('MIN(tp.value)') )->group( - ['e.entity_id', 'cg.customer_group_id', 'cw.website_id'] + ['e.entity_id', 'cg.customer_group_id', 'pw.website_id'] ); if (!empty($entityIds)) { $select->where('e.entity_id IN(?)', $entityIds); } + foreach ($dimensions as $dimension) { + if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) { + throw new \LogicException( + 'Provided dimension is not valid for Price indexer: ' . $dimension->getName() + ); + } + $select->where($this->dimensionToFieldMapper[$dimension->getName()] . ' = ?', $dimension->getValue()); + } - $query = $select->insertFromSelect($this->_getTierPriceIndexTable()); + $query = $select->insertFromSelect($this->getTable('catalog_product_index_tier_price')); $connection->query($query); - - return $this; } /** * Create bundle price. * + * @param IndexTableStructure $priceTable * @return void */ - private function applyBundlePrice(): void + private function applyBundlePrice($priceTable): void { $select = $this->getConnection()->select(); $select->from( - $this->_getBundlePriceTable(), + $this->getBundlePriceTable(), [ 'entity_id', 'customer_group_id', @@ -486,11 +630,10 @@ private function applyBundlePrice(): void 'min_price', 'max_price', 'tier_price', - 'base_tier', ] ); - $query = $select->insertFromSelect($this->_getDefaultFinalPriceTable()); + $query = $select->insertFromSelect($priceTable->getTableName()); $this->getConnection()->query($query); } @@ -498,13 +641,14 @@ private function applyBundlePrice(): void * Make insert/update bundle option price. * * @return void + * @param IndexTableStructure $priceTable */ - private function applyBundleOptionPrice(): void + private function applyBundleOptionPrice($priceTable): void { $connection = $this->getConnection(); $subSelect = $connection->select()->from( - $this->_getBundleOptionTable(), + $this->getBundleOptionTable(), [ 'entity_id', 'customer_group_id', @@ -534,7 +678,47 @@ private function applyBundleOptionPrice(): void ] ); - $query = $select->crossUpdateFromSelect(['i' => $this->_getDefaultFinalPriceTable()]); + $query = $select->crossUpdateFromSelect(['i' => $priceTable->getTableName()]); $connection->query($query); } + + /** + * Get main table + * + * @param array $dimensions + * @return string + */ + private function getMainTable($dimensions) + { + if ($this->fullReindexAction) { + return $this->tableMaintainer->getMainReplicaTable($dimensions); + } + return $this->tableMaintainer->getMainTable($dimensions); + } + + /** + * Get connection + * + * return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \DomainException + */ + private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface + { + if ($this->connection === null) { + $this->connection = $this->resource->getConnection($this->connectionName); + } + + return $this->connection; + } + + /** + * Get table + * + * @param string $tableName + * @return string + */ + private function getTable($tableName) + { + return $this->resource->getTableName($tableName, $this->connectionName); + } } diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index e9295b22674bd..ca6d7f1030121 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -5,10 +5,8 @@ */ namespace Magento\Bundle\Model\ResourceModel\Selection; -use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\DataObject; use Magento\Framework\DB\Select; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\Framework\App\ObjectManager; @@ -45,6 +43,95 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ private $websiteScopePriceJoined = false; + /** + * @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Item + */ + private $stockItem; + + /** + * Collection constructor. + * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory + * @param \Psr\Log\LoggerInterface $logger + * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Eav\Model\Config $eavConfig + * @param \Magento\Framework\App\ResourceConnection $resource + * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory + * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper + * @param \Magento\Framework\Validator\UniversalFactory $universalFactory + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\Module\Manager $moduleManager + * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory + * @param \Magento\Catalog\Model\ResourceModel\Url $catalogUrl + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Framework\Stdlib\DateTime $dateTime + * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement + * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection + * @param ProductLimitationFactory|null $productLimitationFactory + * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool + * @param \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer|null $tableMaintainer + * @param \Magento\CatalogInventory\Model\ResourceModel\Stock\Item|null $stockItem + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Framework\Data\Collection\EntityFactory $entityFactory, + \Psr\Log\LoggerInterface $logger, + \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Eav\Model\Config $eavConfig, + \Magento\Framework\App\ResourceConnection $resource, + \Magento\Eav\Model\EntityFactory $eavEntityFactory, + \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper, + \Magento\Framework\Validator\UniversalFactory $universalFactory, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\Module\Manager $moduleManager, + \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory, + \Magento\Catalog\Model\ResourceModel\Url $catalogUrl, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + \Magento\Customer\Model\Session $customerSession, + \Magento\Framework\Stdlib\DateTime $dateTime, + \Magento\Customer\Api\GroupManagementInterface $groupManagement, + \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, + ProductLimitationFactory $productLimitationFactory = null, + \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, + \Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer $tableMaintainer = null, + \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $stockItem = null + ) { + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $eavConfig, + $resource, + $eavEntityFactory, + $resourceHelper, + $universalFactory, + $storeManager, + $moduleManager, + $catalogProductFlatState, + $scopeConfig, + $productOptionFactory, + $catalogUrl, + $localeDate, + $customerSession, + $dateTime, + $groupManagement, + $connection, + $productLimitationFactory, + $metadataPool, + $tableMaintainer + ); + + $this->stockItem = $stockItem + ?? ObjectManager::getInstance()->get(\Magento\CatalogInventory\Model\ResourceModel\Stock\Item::class); + } + /** * Initialize collection * @@ -170,28 +257,30 @@ public function setPositionOrder() */ public function addQuantityFilter() { - $stockItemTable = $this->getTable('cataloginventory_stock_item'); - $stockStatusTable = $this->getTable('cataloginventory_stock_status'); + $manageStockExpr = $this->stockItem->getManageStockExpr('stock_item'); + $backordersExpr = $this->stockItem->getBackordersExpr('stock_item'); + $minQtyExpr = $this->getConnection()->getCheckSql( + 'selection.selection_can_change_qty', + $this->stockItem->getMinSaleQtyExpr('stock_item'), + 'selection.selection_qty' + ); + + $where = $manageStockExpr . ' = 0'; + $where .= ' OR (' + . 'stock_item.is_in_stock = ' . \Magento\CatalogInventory\Model\Stock::STOCK_IN_STOCK + . ' AND (' + . $backordersExpr . ' != ' . \Magento\CatalogInventory\Model\Stock::BACKORDERS_NO + . ' OR ' + . $minQtyExpr . ' <= stock_item.qty' + . ')' + . ')'; + $this->getSelect() ->joinInner( - ['stock' => $stockStatusTable], - 'selection.product_id = stock.product_id', - [] - )->joinInner( - ['stock_item' => $stockItemTable], + ['stock_item' => $this->stockItem->getMainTable()], 'selection.product_id = stock_item.product_id', [] - ) - ->where( - '(' - . 'selection.selection_can_change_qty > 0' - . ' or ' - . 'selection.selection_qty <= stock.qty' - . ' or ' - .'stock_item.manage_stock = 0' - . ')' - ) - ->where('stock.stock_status = 1'); + )->where($where); return $this; } diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminBundleProductActionGroup.xml index a5e62fca9483c..836826734f02d 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminBundleProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Fill main fields in create product form--> <actionGroup name="fillMainBundleProductForm"> <arguments> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminClearFiltersActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminClearFiltersActionGroup.xml index f3e5eff3834e3..b3ac72d3f416e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminClearFiltersActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminClearFiltersActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminClearFiltersActionGroup"> <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="GoToCatalogProductPage"/> <waitForPageLoad stepKey="WaitForPageToLoad"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/BundleProductFilterActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/BundleProductFilterActionGroup.xml index 8ab7af1d0318e..177f9203ed146 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/BundleProductFilterActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/BundleProductFilterActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="BundleProductFilter"> <!--Setting filter--> <!--Prereq: go to admin product catalog page--> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml index af8fc1459d9e3..6e889ea2432e7 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateBasicBundleProduct"> <!--Prereq: Go to bundle product creation page--> <!--Product name and SKU--> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml index 2ae9748c773e8..e3ac6483bc7bd 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AncillaryPrepBundleProduct"> <!--Prereq: go to bundle product creation page--> <fillField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{BundleProduct.name}}" stepKey="fillProductName"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml new file mode 100644 index 0000000000000..50af5993af5bc --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/SetBundleProductAttributesActionGroup.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SetBundleProductAttributes"> + <arguments> + <argument name="attributeSet" defaultValue="Default" type="string"/> + <argument name="bundleProductName" defaultValue="defaultBundleProductNameAndSku" type="string"/> + <argument name="bundleProductSku" defaultValue="defaultBundleProductNameAndSku" type="string"/> + <argument name="price" defaultValue="10" type="string"/> + <argument name="stock" defaultValue="In Stock" type="string"/> + <argument name="weight" defaultValue="10" type="string"/> + <argument name="visibility" defaultValue="Catalog, Search" type="string"/> + <argument name="fromDate" defaultValue="10/10/2018" type="string"/> + <argument name="toDate" defaultValue="10/10/2018" type="string"/> + <argument name="country" defaultValue="Italy" type="string"/> + </arguments> + + <!-- + Pre-Reqs: + 1) Go to bundle product creation page + 2) Will not Enable/Disable + --> + + <!--Apply Attribute Set--> + <click selector="{{AdminProductFormSection.attributeSet}}" stepKey="startEditAttrSet"/> + <fillField selector="{{AdminProductFormSection.attributeSetFilter}}" userInput="{{attributeSet}}" stepKey="searchForAttrSet"/> + <click selector="{{AdminProductFormSection.attributeSetFilterResult}}" stepKey="selectAttrSet"/> + + <!--Product name and SKU--> + <fillField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{bundleProductName}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{bundleProductSku}}" stepKey="fillProductSku"/> + <click selector="{{AdminProductFormBundleSection.productName}}" stepKey="clickUnselectField"/> + + <!--Dynamic SKU Toggle--> + <checkOption selector="{{AdminProductFormBundleSection.dynamicSkuToggle}}" stepKey="clickOnToggle"/> + <click selector="{{AdminProductFormBundleSection.productName}}" stepKey="clickUnselectFieldAgain"/> + + <!--Dynamic Price Toggle--> + <checkOption selector="{{AdminProductFormBundleSection.dynamicPriceToggle}}" stepKey="clickOnDynamicPriceToggle"/> + + <!--Tax Class--> + <selectOption selector="{{AdminProductFormBundleSection.taxClassDropDown}}" userInput="Taxable Goods" stepKey="taxClassDropDown"/> + + <!--Fill out price--> + <fillField selector="{{AdminProductFormBundleSection.priceField}}" userInput="{{price}}" stepKey="fillOutPrice"/> + + <!--Stock status--> + <selectOption selector="{{AdminProductFormBundleSection.stockStatusField}}" userInput="{{stock}}" stepKey="stockStatus"/> + + <!--Dynamic weight--> + <checkOption selector="{{AdminProductFormBundleSection.dynamicWeightToggle}}" stepKey="dynamicWeight"/> + + <!--Weight--> + <fillField selector="{{AdminProductFormBundleSection.weightField}}" userInput="{{weight}}" stepKey="fillIn"/> + + <!--Visibilty--> + <selectOption selector="{{AdminProductFormBundleSection.visibilityDropDown}}" userInput="{{visibility}}" stepKey="openVisibility"/> + + <!--Categories--> + <click selector="{{AdminProductFormBundleSection.categoriesDropDown}}" stepKey="clickOnCategoriesDropDown"/> + <click selector="{{AdminProductFormBundleSection.defaultCategory}}" stepKey="selectDefaultCategory"/> + <click selector="{{AdminProductFormBundleSection.categoryDone}}" stepKey="clickOnCategoriesDoneToCloseOptions"/> + + <!--New from - to--> + <fillField selector="{{AdminProductFormBundleSection.fromDate}}" userInput="{{fromDate}}" stepKey="fillInFirstDate"/> + <fillField selector="{{AdminProductFormBundleSection.toDate}}" userInput="{{toDate}}" stepKey="fillInSecondDate"/> + + <!--Country of manufacture--> + <selectOption selector="{{AdminProductFormBundleSection.countryOfManufactureDropDown}}" userInput="{{country}}" stepKey="countryOfManufactureDropDown"/> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml new file mode 100644 index 0000000000000..f28ffbdc40acc --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Add Bundle Product to Cart from the category page with specified quantity to cart --> + <actionGroup name="StorefrontAddCategoryBundleProductToCartActionGroup"> + <arguments> + <argument name="product"/> + <argument name="quantity" defaultValue="1" type="string"/> + </arguments> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(product.name)}}" stepKey="moveMouseOverProduct" /> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="clickAddToCart" /> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickCustomizeAndAddToCart"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> + <fillField selector="{{StorefrontBundleProductActionSection.quantityField}}" userInput="{{quantity}}" stepKey="fillBundleProductQuantity"/> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickAddBundleProductToCart"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad3"/> + <waitForText userInput="{{quantity}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml index 1cc0ce147ae0e..60d11345731c1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml @@ -7,13 +7,12 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ApiBundleLink" type="bundle_link"> - <var key="sku" entityKey="sku" entityType="product2"/> - <var key="option_id" entityKey="option_id" entityType="bundle_options"/> + <var key="option_id" entityKey="return" entityType="bundle_option"/> <var key="sku" entityKey="sku" entityType="product"/> <data key="qty">1</data> - <data key="is_default">1</data> + <data key="is_default">0</data> <data key="price">1.11</data> <data key="price_type">1</data> <data key="can_change_quantity">1</data> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleOptionData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleOptionData.xml index 80d9307255e59..a53ae9be4b75b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/BundleOptionData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleOptionData.xml @@ -7,14 +7,35 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="DropdownBundleOption" type="bundle_option"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DropDownBundleOption" type="bundle_option"> <data key="title" unique="suffix">bundle-option-dropdown</data> <data key="required">true</data> - <data key="type">dropdown</data> + <data key="type">select</data> + <data key="position">0</data> + <var key="sku" entityKey="sku" entityType="product2"/> + </entity> + <entity name="RadioButtonsOption" type="bundle_option"> + <data key="title" unique="suffix">bundle-option-radio</data> + <data key="required">true</data> + <data key="type">radio</data> <data key="position">1</data> <var key="sku" entityKey="sku" entityType="product2"/> </entity> + <entity name="CheckboxOption" type="bundle_option"> + <data key="title" unique="suffix">bundle-option-checkbox</data> + <data key="required">true</data> + <data key="type">checkbox</data> + <data key="position">3</data> + <var key="sku" entityKey="sku" entityType="product2"/> + </entity> + <entity name="MultipleSelectOption" type="bundle_option"> + <data key="title" unique="suffix">bundle-option-multipleselect</data> + <data key="required">true</data> + <data key="type">multi</data> + <data key="position">4</data> + <var key="sku" entityKey="sku" entityType="product2"/> + </entity> <entity name="AllBundleOptions" type="bundle_options"> <var key="sku" entityKey="sku" entityType="product"/> </entity> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml index 65ac763460151..e6866ae74a7e1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml @@ -6,7 +6,7 @@ */ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomAttributeDynamicPrice" type="custom_attribute"> <data key="attribute_code">price_type</data> <data key="value">0</data> @@ -19,4 +19,8 @@ <data key="attribute_code">price_view</data> <data key="value">1</data> </entity> + <entity name="CustomAttributePriceViewRange" type="custom_attribute"> + <data key="attribute_code">price_view</data> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index 80ffe13ff3fe4..af93200f946d2 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -7,9 +7,8 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="BundleProduct" type="product"> - <data key="name" unique="suffix">BundleProduct</data> <data key="name" unique="suffix">BundleProduct</data> <data key="name2" unique="suffix">BundleProduct2</data> <data key="sku" unique="suffix">bundleproduct</data> @@ -24,6 +23,7 @@ <data key="urlKey2" unique="suffix">bundleproduct2</data> <data key="default_quantity1">10</data> <data key="default_quantity2">20</data> + <data key="quantity">30</data> <data key="set">4</data> <data key="type">bundle</data> <data key="price">10</data> @@ -45,4 +45,19 @@ <requiredEntity type="custom_attribute">CustomAttributeDynamicPrice</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributePriceView</requiredEntity> </entity> + <entity name="ApiBundleProductPriceViewRange" type="product2"> + <data key="name" unique="suffix">Api Bundle Product</data> + <data key="sku" unique="suffix">api-bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">api-bundle-product</data> + <requiredEntity type="custom_attribute">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute">ApiProductShortDescription</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeDynamicPrice</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributePriceViewRange</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_link-meta.xml b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_link-meta.xml index 435cf59c6cbfd..254f542316d10 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_link-meta.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_link-meta.xml @@ -7,8 +7,8 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> - <operation name="CreateBundleLink" dataType="bundle_link" type="create" auth="adminOauth" url="/V1/bundle-products/{sku}/links/{option_id}" method="POST"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateBundleLink" dataType="bundle_link" type="create" auth="adminOauth" url="/V1/bundle-products/{sku}/links/{return}" method="POST"> <contentType>application/json</contentType> <object dataType="bundle_link" key="linkedProduct"> <field key="sku">string</field> diff --git a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_option-meta.xml b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_option-meta.xml index c912ea5eac41a..4e1dc7ac9cb50 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_option-meta.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_option-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateBundleOption" dataType="bundle_option" type="create" auth="adminOauth" url="/V1/bundle-products/options/add" method="POST"> <contentType>application/json</contentType> <object dataType="bundle_option" key="option"> diff --git a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_options-meta.xml b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_options-meta.xml index 12cba3fc179fe..df931c74191f9 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_options-meta.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Metadata/bundle_options-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="GetAllBundleOptions" dataType="bundle_options" type="get" auth="adminOauth" url="/V1/bundle-products/{sku}/options/all" method="GET"> <contentType>application/json</contentType> </operation> diff --git a/app/code/Magento/Bundle/Test/Mftf/Page/AdminCatalogProductPage.xml b/app/code/Magento/Bundle/Test/Mftf/Page/AdminCatalogProductPage.xml index cb97521499e23..782c97aab1a29 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Page/AdminCatalogProductPage.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Page/AdminCatalogProductPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCatalogProductPage" url="catalog/product/" area="admin" module="Magento_Bundle"> <section name="AdminCatalogProductSection"/> </page> diff --git a/app/code/Magento/Bundle/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Bundle/Test/Mftf/Page/AdminProductCreatePage.xml index f0048e2fc95d4..562ded6c8e40f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Page/AdminProductCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> <section name="AdminProductFormBundleSection"/> </page> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index 0d0edbf857515..814d03c52f4be 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormBundleSection"> <element name="bundleItemsToggle" type="button" selector="//span[text()='Bundle Items']"/> <element name="shipmentType" type="select" selector=".admin__control-select[name='product[shipment_type]']"/> @@ -49,7 +49,9 @@ <!--NameOfProductOnProductPage--> <element name="bundleProductName" type="text" selector="//*[@id='maincontent']//span[@itemprop='name']"/> <!--EnableDisableToggle--> - <element name="enableDisableToggle" type="button" selector="//*[@id='container']//input[@name='product[status]']/.." timeout="30"/> + <element name="enableDisableToggle" type="checkbox" selector="//*[@id='container']//input[@name='product[status]']/.." timeout="30"/> + <element name="enableDisableToggleOn" type="checkbox" selector="//*[@id='container']//input[@name='product[status]' and @value='1']/.."/> + <element name="enableDisableToggleOff" type="checkbox" selector="//*[@id='container']//input[@name='product[status]' and @value='2']/.."/> <!--SearchButton--> <element name="searchButton" type="button" selector="//div[@class='data-grid-search-control-wrap']//*[@type='button']" timeout="30"/> <!--ClickOnFirstProductInCatalog--> @@ -61,12 +63,35 @@ <element name="listedBundleItem2" type="text" selector="//tr[@data-repeat-index='2']//div"/> <!--FirstProductOption--> <element name="firstProductOption" type="checkbox" selector="//div[@class='admin__data-grid-outer-wrap']//tr[@data-repeat-index='0']//input[@type='checkbox']"/> + <element name="dynamicSkuToggle" type="checkbox" selector="div[data-index='sku_type'] .admin__actions-switch-label" timeout="30"/> + <element name="dynamicPriceToggle" type="checkbox" selector="//div[@data-index='price_type']//div[@data-role='switcher']"/> + <element name="taxClassDropDown" type="select" selector="//select[@name='product[tax_class_id]']" timeout="30"/> + <element name="taxableGoodsOption" type="text" selector="//select[@name='product[tax_class_id]']//option[@data-title='Taxable Goods']"/> + <element name="stockStatusField" type="select" selector="//select[@name='product[quantity_and_stock_status][is_in_stock]']"/> + <element name="inStockOption" type="text" selector="//select[@name='product[quantity_and_stock_status][is_in_stock]']//option[@data-title='{{stock}}']" parameterized="true" timeout="30"/> + <element name="dynamicWeightToggle" type="checkbox" selector="//div[@data-index='weight_type']//div[@data-role='switcher']" timeout="30"/> + <element name="weightField" type="input" selector="//div[@data-index='weight']//input"/> + <element name="categoriesDropDown" type="block" selector="//div[@data-index='category_ids']//div[@class='admin__field-control']" timeout="30"/> + <element name="defaultCategory" type="text" selector="//div[@data-index='category_ids']//span[contains(text(), 'Default Category')]"/> + <element name="visibilityDropDown" type="select" selector="//select[@name='product[visibility]']"/> + <element name="catalogOption1" type="text" selector="//select[@name='product[visibility]']//option[1]"/> + <element name="catalogOption2" type="text" selector="//select[@name='product[visibility]']//option[2]"/> + <element name="fromDate" type="input" selector="//div[@data-index='news_from_date']//input"/> + <element name="toDate" type="input" selector="//div[@data-index='news_to_date']//input"/> + <element name="countryOfManufactureDropDown" type="select" selector="//select[@name='product[country_of_manufacture]']"/> + <element name="selectCountryOfManufacture" type="text" selector="//select[@name='product[country_of_manufacture]']//option[@data-title='{{country}}']" parameterized="true"/> + <element name="dynamicSkuToggleOn" type="checkbox" selector="//div[@data-index='sku_type']//div[@data-role='switcher']//input[@value='0']"/> + <element name="dynamicSkuToggleOff" type="checkbox" selector="//div[@data-index='sku_type']//div[@data-role='switcher']//input[@value='1']"/> + <element name="dynamicWeightToggleOn" type="checkbox" selector="//div[@data-index='weight_type']//div[@data-role='switcher']//input[@value='0']"/> + <element name="dynamicWeightToggleOff" type="checkbox" selector="//div[@data-index='weight_type']//div[@data-role='switcher']//input[@value='1']"/> + <element name="categoryFieldName" type="text" selector="//fieldset[@data-index='container_category_ids']//label//span" timeout="30"/> + <element name="categoryDone" type="button" selector=".admin__action-multiselect-actions-wrap [type='button'] span" timeout="30"/> + <element name="dynamicPriceToggleOff" type="checkbox" selector="//div[@data-index='price_type']//div[@data-role='switcher']//input[@value='1']"/> <!--Category Selection--> - <element name="categoriesDropDown" type="multiselect" selector="//div[@data-index='category_ids']//div" timeout="30"/> - <element name="defaultCategory" type="multiselect" selector="//div[@data-index='category_ids']//span[contains(text(), 'Default Category')]"/> <element name="categoryByName" type="multiselect" selector="//div[@data-index='category_ids']//span[contains(text(), '{{category}}')]" parameterized="true"/> <element name="searchForCategory" type="input" selector="div.action-menu._active > div.admin__action-multiselect-search-wrap input" timeout="30"/> <element name="selectCategory" type="multiselect" selector="//div[@class='action-menu _active']//label[@class='admin__action-multiselect-label']"/> <element name="categoriesLabel" type="text" selector="//div[@class='action-menu _active']//button[@data-action='close-advanced-select']"/> + <element name="userDefinedQuantity" type="checkbox" selector="[name='bundle_options[bundle_options][{{option}}][bundle_selections][{{product}}][selection_can_change_qty]'][type='checkbox']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/BundleStorefrontSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/BundleStorefrontSection.xml index 9f4e6e04ac351..7a188fd58e1af 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/BundleStorefrontSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/BundleStorefrontSection.xml @@ -7,11 +7,11 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="BundleStorefrontSection"> <!--TestingForLocationOfOptions--> - <element name="bundleOptionSelector" type="button" selector="//*[@id='bundle-slide']/span"/> - <element name="bundleOptionSelection" type="button" selector="//div[@class='nested options-list']/div[2]/label[@class='label']"/> + <element name="bundleOptionSelector" type="checkbox" selector="//*[@id='bundle-slide']/span" timeout="30"/> + <element name="bundleOptionSelection" type="checkbox" selector="//div[@class='nested options-list']/div[{{optionNumber}}]/label[@class='label']" parameterized="true"/> <!--Description--> <!--CE exclusively--> <element name="longDescriptionText" type="text" selector="//*[@id='description']/div/div" timeout="30"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index b462fcd11cfe7..8d9f29814f762 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBundledSection"> <element name="nthBundledOption" type="input" selector=".option:nth-of-type({{numOption}}) .choice:nth-of-type({{numOptionSelect}}) input" parameterized="true"/> <element name="addToCart" type="button" selector="#bundle-slide" timeout="30"/> @@ -21,5 +21,13 @@ <element name="nthOptionDiv" type="block" selector="#product-options-wrapper div.field.option:nth-of-type({{var}})" parameterized="true"/> <element name="nthItemOptionsTitle" type="text" selector="dl.item-options dt:nth-of-type({{var}})" parameterized="true"/> <element name="nthItemOptionsValue" type="text" selector="dl.item-options dd:nth-of-type({{var}})" parameterized="true"/> + <element name="bundleProductName" type="text" selector="//*[@id='maincontent']//span[@itemprop='name']"/> + <element name="pageNotFound" type="text" selector="//h1[@class='page-title']//span[contains(., 'Whoops, our bad...')]"/> + <element name="dropDownOptionOneProducts" type="select" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//select" parameterized="true"/> + <element name="dropDownOptionOneQuantity" type="input" selector="//span[contains(text(), '{{productName}}')]/../..//input" parameterized="true"/> + <element name="radioButtonOptionTwoProducts" type="checkbox" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field choice'][{{productNumber}}]/input" parameterized="true"/> + <element name="radioButtonOptionTwoQuantity" type="input" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field qty qty-holder']//input" parameterized="true"/> + <element name="checkboxOptionThreeProducts" type="checkbox" selector="//label//span[contains(text(), '{{productName}}')]/../..//div[@class='control']//div[@class='field choice'][{{productNumber}}]/input" parameterized="true"/> + <element name="multiselectOptionFourProducts" type="multiselect" selector="//label//span[contains(text(), '{{productName}}')]/../..//select[@multiple='multiple']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontCategoryProductSection.xml new file mode 100644 index 0000000000000..3d5dc61d88a87 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontCategoryProductSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCategoryProductSection"> + <element name="priceToByProductId" type="text" selector="div[data-product-id='{{id}}'] .price-to" parameterized="true"/> + <element name="priceFromByProductId" type="text" selector="div[data-product-id='{{id}}'] .price-from" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml new file mode 100644 index 0000000000000..9dc4aed26bef0 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontBundleProductActionSection"> + <element name="customizeAndAddToCartButton" type="button" selector="#bundle-slide"/> + <element name="quantityField" type="input" selector="#qty"/> + <element name="addToCartButton" type="button" selector="#product-addtocart-button"/> + </section> +</sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml new file mode 100644 index 0000000000000..735571375866e --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontProductInfoMainSection"> + <element name="priceFrom" type="text" selector=".product-info-price .price-from"/> + <element name="priceTo" type="text" selector=".product-info-price .price-to"/> + <element name="minPrice" type="text" selector="span[data-price-type='minPrice']"/> + <element name="maxPrice" type="text" selector="span[data-price-type='minPrice']"/> + </section> +</sections> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml index d94e196ea5ad1..401d360a34c64 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddBundleItemsTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml index e1f90790b30a9..21e6be98b3169 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultImageBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultImageBundleProductTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml new file mode 100644 index 0000000000000..3c00344697699 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoBundleProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <features value="Bundle"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add default video for a Bundle Product"/> + <description value="Admin should be able to add default video for a Bundle Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-110"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Create a bundle product --> + <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillMainProductForm"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add two bundle items --> + <scrollTo selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="openBundleSection" after="scrollToSection"/> + <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption" after="openBundleSection"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleTitle" after="clickAddOption"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillBundleTitle" after="waitForBundleTitle"/> + <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectOptionBundleTitle" after="fillBundleTitle"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProducts" after="selectOptionBundleTitle"/> + <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProducts" after="waitForAddProducts"/> + <waitForPageLoad stepKey="waitForPageLoad" after="clickAddProducts"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku1" after="waitForPageLoad"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku2" after="checkOption1"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> + <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="addProducts" after="checkOption2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty1" after="addProducts"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty2" before="saveProductForm"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml index 795982eb4b939..1d2f21b7d15f9 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAttributeSetSelectionTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAttributeSetSelectionTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml new file mode 100644 index 0000000000000..3a70b189b4dce --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml @@ -0,0 +1,192 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminBasicBundleProductAttributesTest"> + <annotations> + <features value="Bundle"/> + <stories value="Create/Edit bundle product in Admin"/> + <title value="Admin should be able to set/edit all the basic product attributes when creating/editing a bundle product"/> + <description value="Admin should be able to set/edit all the basic product attributes when creating/editing a bundle product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-222"/> + <skip> + <issueId value="MQE-1214"/> + </skip> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + <!--Create attribute set--> + <actionGroup ref="CreateDefaultAttributeSet" stepKey="createDefaultAttributeSet"> + <argument name="label" value="{{ProductAttributeFrontendLabel.label}}"/> + </actionGroup> + + <!--Go to product creation page--> + <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> + <waitForPageLoad stepKey="waitForBundleProductCreationPage"/> + + <!--Enable/Disable Toggle--> + <checkOption selector="{{AdminProductFormBundleSection.enableDisableToggle}}" stepKey="clickOnEnableDisableToggle"/> + + <!--Fill out product attributes--> + <actionGroup ref="SetBundleProductAttributes" stepKey="fillOutAllAttributes"> + <!--primarily uses default values--> + <argument name="attributeSet" value="{{ProductAttributeFrontendLabel.label}}"/> + <argument name="bundleProductName" value="{{BundleProduct.name}}"/> + <argument name="bundleProductSku" value="{{BundleProduct.sku}}"/> + <argument name="visibilty" value="catalog"/> + </actionGroup> + + <!--Verify form was filled out correctly--> + + <!--Enable/Disable Toggle check--> + <dontSeeCheckboxIsChecked selector="{{AdminProductFormBundleSection.enableDisableToggle}}" stepKey="seeToggleIsOff"/> + + <!--Apply Attribute Set--> + <seeOptionIsSelected selector="{{AdminProductFormSection.attributeSet}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeSet"/> + + <!--Product name and SKU--> + <seeInField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{BundleProduct.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku}}" stepKey="seeProductSku"/> + + <!--Dynamic SKU Toggle--> + <dontSeeCheckboxIsChecked selector="{{AdminProductFormBundleSection.dynamicSkuToggle}}" stepKey="seeDynamicSkuToggleOff"/> + + <!--Dynamic Price Toggle--> + <dontSeeCheckboxIsChecked selector="{{AdminProductFormBundleSection.dynamicPriceToggle}}" stepKey="seeDynamicPriceToggleOff"/> + + <!--Tax Class--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.taxClassDropDown}}" userInput="Taxable Goods" stepKey="seeCorrectTaxClass"/> + + <!--Fill out price--> + <seeInField selector="{{AdminProductFormBundleSection.priceField}}" userInput="10" stepKey="seePrice"/> + + <!--Stock status--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.stockStatusField}}" userInput="In Stock" stepKey="seeStockStatus"/> + + <!--Dynamic weight--> + <dontSeeCheckboxIsChecked selector="{{AdminProductFormBundleSection.dynamicWeightToggle}}" stepKey="seeDynamicWeightOff"/> + + <!--Weight--> + <seeInField selector="{{AdminProductFormBundleSection.weightField}}" userInput="10" stepKey="seeWeight"/> + + <!--Visibilty--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.visibilityDropDown}}" userInput="Catalog" stepKey="seeVisibility"/> + + <!--Categories--> + <seeElement selector="{{AdminProductFormBundleSection.defaultCategory}}" stepKey="seeDefaultCategory"/> + + <!--New from - to--> + <seeInField selector="{{AdminProductFormBundleSection.fromDate}}" userInput="10/10/2018" stepKey="seeFirstDate"/> + <seeInField selector="{{AdminProductFormBundleSection.toDate}}" userInput="10/10/2018" stepKey="seeSecondDate"/> + + <!--Country of manufacture--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.countryOfManufactureDropDown}}" userInput="Italy" stepKey="seeCountryOfManufacture"/> + + <!--Create second attribute set for edit--> + <actionGroup ref="CreateDefaultAttributeSet" stepKey="createSecondAttributeSet"> + <argument name="label" value="{{ProductAttributeFrontendLabelTwo.label}}"/> + </actionGroup> + + <!--Filter catalog--> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <waitForPageLoad stepKey="WaitForPageToLoad"/> + <actionGroup ref="filterProductGridByName" stepKey="filterBundleProductOptionsDownToName"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <click selector="{{AdminProductFiltersSection.attributeSetOfFirstRow(ProductAttributeFrontendLabel.label)}}" stepKey="clickAttributeSet2"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + + <!--Edit fields--> + + <!--Enable/Disable Toggle--> + <checkOption selector="{{AdminProductFormBundleSection.enableDisableToggle}}" stepKey="clickOnEnableDisableToggleAgain"/> + + <!--Apply Attribute Set--> + <click selector="{{AdminProductFormSection.attributeSet}}" stepKey="startEditAttrSet"/> + <fillField selector="{{AdminProductFormSection.attributeSetFilter}}" userInput="{{ProductAttributeFrontendLabelTwo.label}}" stepKey="searchForAttrSet"/> + <click selector="{{AdminProductFormSection.attributeSetFilterResultByName(ProductAttributeFrontendLabelTwo.label)}}" stepKey="selectAttrSet"/> + + <!--Product name and SKU--> + <fillField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{BundleProduct.name2}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku2}}" stepKey="fillProductSku"/> + <click selector="{{AdminProductFormBundleSection.productName}}" stepKey="clickUnselectField"/> + + <!--Dynamic SKU Toggle--> + <checkOption selector="{{AdminProductFormBundleSection.dynamicSkuToggle}}" stepKey="clickOnToggle"/> + <click selector="{{AdminProductFormBundleSection.productName}}" stepKey="clickUnselectFieldAgain"/> + + <!--Fill out price--> + <fillField selector="{{AdminProductFormBundleSection.priceField}}" userInput="20" stepKey="fillOutPrice"/> + + <!--Stock status--> + <selectOption selector="{{AdminProductFormBundleSection.stockStatusField}}" userInput="Out of Stock" stepKey="stockStatus"/> + + <!--Dynamic weight--> + <checkOption selector="{{AdminProductFormBundleSection.dynamicWeightToggle}}" stepKey="dynamicWeight"/> + + <!--Visibilty--> + <selectOption selector="{{AdminProductFormBundleSection.visibilityDropDown}}" userInput="Not Visible Individually" stepKey="openVisibility"/> + + <!--New from - to--> + <fillField selector="{{AdminProductFormBundleSection.fromDate}}" userInput="10/20/2018" stepKey="fillInFirstDate"/> + <fillField selector="{{AdminProductFormBundleSection.toDate}}" userInput="10/20/2018" stepKey="fillInSecondDate"/> + + <!--Country of manufacture--> + <selectOption selector="{{AdminProductFormBundleSection.countryOfManufactureDropDown}}" userInput="France" stepKey="countryOfManufactureDropDown"/> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> + + <!--Verify form was filled out correctly after edit--> + + <!--Enable/Disable Toggle--> + <seeElement selector="{{AdminProductFormBundleSection.enableDisableToggleOn}}" stepKey="seeToggleIsOn2"/> + + <!--Attribute Set--> + <seeOptionIsSelected selector="{{AdminProductFormSection.attributeSet}}" userInput="{{ProductAttributeFrontendLabelTwo.label}}" stepKey="seeAttributeSet2"/> + + <!--Product name and SKU--> + <seeInField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{BundleProduct.name2}}" stepKey="seeProductName2"/> + <seeInField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku2}}" stepKey="seeProductSku2"/> + + <!--Dynamic SKU Toggle--> + <seeElement selector="{{AdminProductFormBundleSection.dynamicSkuToggleOn}}" stepKey="seeDynamicSkuToggleOn2"/> + + <!--Tax Class--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.taxClassDropDown}}" userInput="Taxable Goods" stepKey="seeCorrectTaxClass2"/> + + <!--Price--> + <seeInField selector="{{AdminProductFormBundleSection.priceField}}" userInput="20" stepKey="seePrice2"/> + + <!--Stock status--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.stockStatusField}}" userInput="Out of Stock" stepKey="seeStockStatus2"/> + + <!--Dynamic weight--> + <seeElement selector="{{AdminProductFormBundleSection.dynamicWeightToggleOn}}" stepKey="seeDynamicWeightOn2"/> + + <!--Visibilty--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.visibilityDropDown}}" userInput="Not Visible Individually" stepKey="seeVisibility2"/> + + <!--Categories--> + <seeElement selector="{{AdminProductFormBundleSection.categoriesDropDown}}" stepKey="seeDefaultCategory2"/> + + <!--New from - to--> + <seeInField selector="{{AdminProductFormBundleSection.fromDate}}" userInput="10/20/2018" stepKey="seeFirstDate2"/> + <seeInField selector="{{AdminProductFormBundleSection.toDate}}" userInput="10/20/2018" stepKey="seeSecondDate2"/> + + <!--Country of manufacture--> + <seeOptionIsSelected selector="{{AdminProductFormBundleSection.countryOfManufactureDropDown}}" userInput="France" stepKey="seeCountryOfManufacture2"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductSetEditContentTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductSetEditContentTest.xml new file mode 100644 index 0000000000000..65733a5bcc037 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBundleProductSetEditContentTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminBundleProductSetEditContentTest" extends="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="Bundle"/> + <stories value="Create/Edit bundle product in Admin"/> + <title value="Admin should be able to set/edit product Content when editing a bundle product"/> + <description value="Admin should be able to set/edit product Content when editing a bundle product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3343"/> + <group value="Bundle"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <after> + <!-- Delete bundle product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillProductForm"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!--Checking content storefront--> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml index 3f88a9f277105..86db6f372b5f8 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminDeleteABundleProduct"> <annotations> <features value="Bundle"/> @@ -82,6 +82,6 @@ <!--Testing deletion of product--> <amOnPage url="{{BundleProduct.urlKey}}.html" stepKey="GoToProductPageAgain"/> <waitForPageLoad stepKey="WaitForProductPageToLoadToShowElement"/> - <dontSeeElement selector="{{BundleStorefrontSection.bundleProductName}}" stepKey="LookingForNameOfProductTwo"/> + <dontSeeElement selector="{{StorefrontBundledSection.bundleProductName}}" stepKey="LookingForNameOfProductTwo"/> </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml new file mode 100644 index 0000000000000..08faa9d2444df --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminEditRelatedBundleProductTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminEditRelatedBundleProductTest"> + <annotations> + <features value="Bundle"/> + <stories value="Create/Edit bundle product in Admin"/> + <title value="Admin should be able to set/edit Related Products information when editing a bundle product"/> + <description value="Admin should be able to set/edit Related Products information when editing a bundle product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3342"/> + <group value="Bundle"/> + </annotations> + <before> + <!--Admin login--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct0"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + </before> + <after> + <!-- Delete the bundled product --> + <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <!--Logging out--> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <deleteData createDataKey="simpleProduct0" stepKey="deleteSimpleProduct0"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + </after> + + <!-- Create a bundle product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageBundle"/> + <waitForPageLoad stepKey="waitForProductPageLoadBundle"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateBundleProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillBundleProductNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct0"> + <argument name="sku" value="$$simpleProduct0.sku$$"/> + </actionGroup> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown"/> + + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct1"> + <argument name="sku" value="$$simpleProduct1.sku$$"/> + </actionGroup> + + <!--Remove previous related product--> + <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.removeRelatedProduct($$simpleProduct0.sku$$)}}" stepKey="removeRelatedProduct"/> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButtonAfterEdit"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShownAgain"/> + + <!--See related product in admin--> + <scrollTo selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" stepKey="scrollTo"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedSee"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedRelatedProduct}}" userInput="$$simpleProduct1.sku$$" stepKey="seeRelatedProduct"/> + + <!--See related product in storefront--> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <see userInput="$$simpleProduct1.sku$$" stepKey="seeRelatedProductInStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml index 9faf9e69bc873..40a6e1b75c60a 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminFilterProductListByBundleProduct"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml index 708580b3c9940..c0edbf14e894b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassDeleteBundleProductsTest"> <annotations> <features value="Bundle"/> @@ -133,11 +133,11 @@ <!--Testing deletion of products--> <amOnPage url="{{BundleProduct.urlKey}}.html" stepKey="GoToProductPageAgain"/> <waitForPageLoad stepKey="WaitForProductPageToLoadToShowElement"/> - <dontSeeElement stepKey="LookingForNameOfProduct" selector="{{BundleStorefrontSection.bundleProductName}}"/> - <seeElement stepKey="LookingForPageNotFoundMessage" selector="{{BundleStorefrontSection.pageNotFound}}"/> + <dontSeeElement stepKey="LookingForNameOfProduct" selector="{{StorefrontBundledSection.bundleProductName}}"/> + <seeElement stepKey="LookingForPageNotFoundMessage" selector="{{StorefrontBundledSection.pageNotFound}}"/> <amOnPage url="{{BundleProduct.urlKey2}}.html" stepKey="GoToProductPageAgain2"/> <waitForPageLoad stepKey="WaitForProductPageToLoadToShowElement2"/> - <dontSeeElement stepKey="LookingForNameOfProduct2" selector="{{BundleStorefrontSection.bundleProductName}}"/> - <seeElement stepKey="LookingForPageNotFoundMessage2" selector="{{BundleStorefrontSection.pageNotFound}}"/> + <dontSeeElement stepKey="LookingForNameOfProduct2" selector="{{StorefrontBundledSection.bundleProductName}}"/> + <seeElement stepKey="LookingForPageNotFoundMessage2" selector="{{StorefrontBundledSection.pageNotFound}}"/> </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml index 5e7a098790724..f87897bd579a3 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminProductBundleCreationTest"> <annotations> <features value="Bundle"/> @@ -72,6 +72,6 @@ <!--Test Assertion - on correct page/page has been published--> <waitForPageLoad stepKey="waitForBundleProductPageToLoad"/> - <seeElement stepKey="LookingForNameOfProduct" selector="{{BundleStorefrontSection.bundleProductName}}"/> + <seeElement stepKey="LookingForNameOfProduct" selector="{{StorefrontBundledSection.bundleProductName}}"/> </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml index 9ce345cde681c..1438958b92b61 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultImageBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageBundleProductTest"> <annotations> <features value="Bundle"/> @@ -74,9 +74,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml new file mode 100644 index 0000000000000..e3cb68b6664e2 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoBundleProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="Bundle"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to remove default video from a Bundle Product"/> + <description value="Admin should be able to remove default video from a Bundle Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-205"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Create a bundle product --> + <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillMainProductForm"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add two bundle items --> + <scrollTo selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="openBundleSection" after="scrollToSection"/> + <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption" after="openBundleSection"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleTitle" after="clickAddOption"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillBundleTitle" after="waitForBundleTitle"/> + <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectOptionBundleTitle" after="fillBundleTitle"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProducts" after="selectOptionBundleTitle"/> + <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProducts" after="waitForAddProducts"/> + <waitForPageLoad stepKey="waitForPageLoad" after="clickAddProducts"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku1" after="waitForPageLoad"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku2" after="checkOption1"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> + <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="addProducts" after="checkOption2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty1" after="addProducts"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillQty2" before="saveProductForm"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml new file mode 100644 index 0000000000000..0b220efaad49f --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml @@ -0,0 +1,195 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product name"/> + <description value="Guest customer should be able to advance search Bundle product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-139"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchBundleBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product sku"/> + <description value="Guest customer should be able to advance search Bundle product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-143"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchBundleByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product description"/> + <description value="Guest customer should be able to advance search Bundle product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-242"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchBundleByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product short description"/> + <description value="Guest customer should be able to advance search Bundle product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-250"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchBundleByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product price"/> + <description value="Guest customer should be able to advance search Bundle product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-251"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <getData entity="GetProduct" stepKey="arg1"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg2"> + <requiredEntity createDataKey="simple1"/> + </getData> + <getData entity="GetProduct" stepKey="arg3"> + <requiredEntity createDataKey="simple2"/> + </getData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml index a579460906d0e..574c0dccdb07f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="BundleProductFixedPricingTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/EnableDisableBundleProductStatusTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/EnableDisableBundleProductStatusTest.xml index d967143258918..0cfd1f99a8ce0 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/EnableDisableBundleProductStatusTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/EnableDisableBundleProductStatusTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EnableDisableBundleProductStatusTest"> <annotations> <features value="Bundle"/> @@ -72,16 +72,21 @@ <!--Go to page--> <amOnPage url="{{BundleProduct.urlKey}}.html" stepKey="GoToProductPage"/> <waitForPageLoad stepKey="waitForBundleProductPageToLoad"/> - <seeElement stepKey="LookingForNameOfProduct" selector="{{BundleStorefrontSection.bundleProductName}}"/> + <seeElement stepKey="LookingForNameOfProduct" selector="{{StorefrontBundledSection.bundleProductName}}"/> <!--Testing disabled view--> - <actionGroup ref="FindProductToEdit" stepKey="FindProductEditPage"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="GoToProductCatalog"/> + <waitForPageLoad stepKey="WaitForCatalogProductPageToLoad"/> + <actionGroup ref="filterProductGridBySku" stepKey="FindProductEditPage"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <click selector="{{AdminDataGridTableSection.rowViewAction('1')}}" stepKey="ClickProductInGrid"/> <click stepKey="ClickOnEnableDisableToggle" selector="{{AdminProductFormBundleSection.enableDisableToggle}}"/> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButtonAgain"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown2"/> <waitForPageLoad stepKey="PauseForSave"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShownAgain"/> <amOnPage url="{{BundleProduct.urlKey}}.html" stepKey="GoToProductPageAgain"/> <waitForPageLoad stepKey="WaitForProductPageToLoadToShowElement"/> - <dontSeeElement stepKey="LookingForNameOfProductTwo" selector="{{BundleStorefrontSection.bundleProductName}}"/> + <dontSeeElement stepKey="LookingForNameOfProductTwo" selector="{{StorefrontBundledSection.bundleProductName}}"/> </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/EndToEndB2CAdminTest.xml index 9402d1d48012f..9040d675be34f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <!--Create Bundle Product--> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageBundle" after="seeSimpleProductInGrid"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml index 27d95cfb5cfa5..0fb8a7b88250c 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="MassEnableDisableBundleProductsTest"> <annotations> <features value="Bundle"/> @@ -17,8 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-217"/> <group value="Bundle"/> - <!--Skipped due to MAGETWO-92898--> - <group value="skip"/> + <skip> + <issueId value="MAGETWO-92898"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -137,7 +138,7 @@ <!--Confirm bundle products have been disabled--> <amOnPage url="{{BundleProduct.urlKey2}}.html" stepKey="GoToProductPage"/> <waitForPageLoad stepKey="WaitForProductPageToLoadToShowElement"/> - <dontSeeElement stepKey="LookingForNameOfProductDisabled" selector="{{BundleStorefrontSection.bundleProductName}}"/> + <dontSeeElement stepKey="LookingForNameOfProductDisabled" selector="{{StorefrontBundledSection.bundleProductName}}"/> <!--Enabling bundle products--> <amOnPage url="{{ProductCatalogPage.url}}" stepKey="GoToCatalogPageChangingView"/> @@ -154,6 +155,6 @@ <!--Confirm bundle products have been enabled--> <amOnPage url="{{BundleProduct.urlKey2}}.html" stepKey="GoToProductPageEnabled"/> <waitForPageLoad stepKey="waitForBundleProductPageToLoad"/> - <seeElement stepKey="LookingForNameOfProduct" selector="{{BundleStorefrontSection.bundleProductName}}"/> + <seeElement stepKey="LookingForNameOfProduct" selector="{{StorefrontBundledSection.bundleProductName}}"/> </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml index 8a0a1ceaf52c7..e0a6a9afd648e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/NewBundleProductSelectionTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="NewBundleProductSelectionTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/NewProductsListWidgetBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/NewProductsListWidgetBundleProductTest.xml new file mode 100644 index 0000000000000..8efe32a7d84c0 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/NewProductsListWidgetBundleProductTest.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetBundleProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="Bundle"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Bundle Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Bundle Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-123"/> + <group value="Bundle"/> + <group value="WYSIWYGDisabled"/> + </annotations> + + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Create a product to appear in the widget, fill in basic info first --> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <click selector="{{AdminProductGridActionSection.addBundleProduct}}" stepKey="clickAddBundleProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + + <!-- and then configure bundled items for this product --> + + <scrollTo selector="{{AdminProductFormBundleSection.addOption}}" stepKey="scrollToAddOptionButton"/> + <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption"/> + <waitForPageLoad stepKey="waitForOptions"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="MFTF Test Bundle 1" stepKey="fillOptionTitle"/> + <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="checkbox" stepKey="selectInputType"/> + <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProductsToOption"/> + <waitForPageLoad stepKey="waitForPageLoadAfterBundleProducts"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow2"/> + <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="clickAddSelectedBundleProducts"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="1" stepKey="fillProductDefaultQty1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="1" stepKey="fillProductDefaultQty2"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here, via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{_defaultProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml index c0d659f1665a8..40132ea956584 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdminEditDataTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontAdminEditDataTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml index 655081df61073..695c3a8bf7dbb 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontBundleCartTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml new file mode 100644 index 0000000000000..285503465a011 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontBundleProductDetailsTest"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle product details page"/> + <title value="Customer should be able to see basic bundle product details"/> + <description value="Customer should be able to see basic bundle product details"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-230"/> + <group value="Bundle"/> + </annotations> + <before> + <!--Creating Data--> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + + <!-- Admin Login--> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdmin"/> + <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + </before> + <after> + <deleteData createDataKey="createPreReqCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- go to bundle product creation page--> + <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage" /> + <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> + + <!--Add description--> + <click selector="{{AdminProductFormBundleSection.contentDropDown}}" stepKey="openDescriptionDropDown"/> + <scrollTo selector="{{AdminProductFormBundleSection.contentDropDown}}" stepKey="scrollToError"/> + <fillField selector="{{AdminProductFormBundleSection.longDescription}}" userInput="This is the long description" stepKey="fillLongDescription"/> + <fillField selector="{{AdminProductFormBundleSection.shortDescription}}" userInput="This is the short description" stepKey="fillShortDescription"/> + + <!-- Add options --> + <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="conditionallyOpenSectionBundleItems"/> + <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleOptions"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillOptionTitle"/> + <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectInputType"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProductsToBundle"/> + <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProductsToOption"/> + <waitForPageLoad stepKey="waitForPageLoadAfterBundleProducts"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow2"/> + <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="clickAddSelectedBundleProducts"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty2"/> + + <!--Create a basic bundle product--> + <actionGroup ref="CreateBasicBundleProduct" stepKey="createBundledProductForTwoSimpleProducts"> + <argument name="bundleProduct" value="BundleProduct"/> + </actionGroup> + + <!--save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButtonAgain"/> + <see userInput="You saved the product." stepKey="messageYouSavedTheProductIsShownAgain"/> + + <!--Checking details--> + <amOnPage url="{{BundleProduct.urlKey}}.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageToLoad"/> + <see selector="{{BundleStorefrontSection.shortDescriptionText}}" userInput="This is the short description" stepKey="seeShortDescription"/> + <see selector="{{BundleStorefrontSection.longDescriptionText}}" userInput="This is the long description" stepKey="seeLongDescription"/> + <click selector="{{BundleStorefrontSection.bundleOptionSelector}}" stepKey="clickOnCustomizationOption"/> + <see selector="{{BundleStorefrontSection.bundleOptionSelection('1')}}" userInput="{{BundleProduct.defaultQuantity}} x $$simpleProduct1.name$$" stepKey="seeOption1"/> + <see selector="{{BundleStorefrontSection.bundleOptionSelection('2')}}" userInput="{{BundleProduct.defaultQuantity}} x $$simpleProduct2.name$$" stepKey="seeOption2"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml index 577079965cabb..9ad4b6828d6e4 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontBundleProductShownInCategoryListAndGrid"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml new file mode 100644 index 0000000000000..5e6e891541420 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCustomerSelectAndSetBundleOptionsTest.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerSelectAndSetBundleOptionsTest"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle product details page"/> + <title value="Customer should be able to select customisable bundle product options and set quantity for them"/> + <description value="Customer should be able to select customisable bundle product options and set quantity for them"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-231"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <!-- Delete the bundled product --> + <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Start creating a bundle product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add Option One, a "Drop-down" type option --> + <actionGroup ref="addBundleOptionWithTwoProducts" stepKey="addBundleOptionWithTwoProducts1"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="select"/> + </actionGroup> + <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('0', '0')}}" stepKey="userDefinedQuantitiyOption0Product0"/> + <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('0', '1')}}" stepKey="userDefinedQuantitiyOption0Product1"/> + + <!-- Add Option Two, a "Radio Buttons" type option --> + <actionGroup ref="addBundleOptionWithTwoProducts" stepKey="addBundleOptionWithTwoProducts2"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Two"/> + <argument name="inputType" value="radio"/> + </actionGroup> + <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('1', '0')}}" stepKey="userDefinedQuantitiyOption1Product0"/> + <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('1', '1')}}" stepKey="userDefinedQuantitiyOption1Product1"/> + + <!-- Add Option Three, a "Checkbox" type option --> + <actionGroup ref="addBundleOptionWithTwoProducts" stepKey="addBundleOptionWithTwoProducts3"> + <argument name="x" value="2"/> + <argument name="n" value="3"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Three"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Add Option Four, a "Multi Select" type option --> + <actionGroup ref="addBundleOptionWithTwoProducts" stepKey="addBundleOptionWithTwoProducts4"> + <argument name="x" value="3"/> + <argument name="n" value="4"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Four"/> + <argument name="inputType" value="multi"/> + </actionGroup> + + <!-- Save product and go to storefront --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!--Select options - set quantities--> + + <!--"Drop-down" type option--> + <selectOption selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Option One')}}" userInput="$$simpleProduct1.sku$$ +$$$simpleProduct1.price$$.00" stepKey="selectOption0Product0"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Option One')}}" userInput="$$simpleProduct1.sku$$ +$$$simpleProduct1.price$$.00" stepKey="checkOption0Product0"/> + <fillField selector="{{StorefrontBundledSection.dropDownOptionOneQuantity('Option One')}}" userInput="3" stepKey="fillQuantity00"/> + <seeInField selector="{{StorefrontBundledSection.dropDownOptionOneQuantity('Option One')}}" userInput="3" stepKey="checkQuantity00"/> + + <selectOption selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Option One')}}" userInput="$$simpleProduct2.sku$$ +$$$simpleProduct2.price$$.00" stepKey="selectOption0Product1"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.dropDownOptionOneProducts('Option One')}}" userInput="$$simpleProduct2.sku$$ +$$$simpleProduct2.price$$.00" stepKey="checkOption0Product1"/> + <fillField selector="{{StorefrontBundledSection.dropDownOptionOneQuantity('Option One')}}" userInput="3" stepKey="fillQuantity01"/> + <seeInField selector="{{StorefrontBundledSection.dropDownOptionOneQuantity('Option One')}}" userInput="3" stepKey="checkQuantity01"/> + + <!--"Radio Buttons" type option--> + <checkOption selector="{{StorefrontBundledSection.radioButtonOptionTwoProducts('Option Two', '1')}}" stepKey="selectOption1Product0"/> + <seeCheckboxIsChecked selector="{{StorefrontBundledSection.radioButtonOptionTwoProducts('Option Two', '1')}}" stepKey="checkOption1Product0"/> + <fillField selector="{{StorefrontBundledSection.radioButtonOptionTwoQuantity('Option Two')}}" userInput="3" stepKey="fillQuantity10"/> + <seeInField selector="{{StorefrontBundledSection.radioButtonOptionTwoQuantity('Option Two')}}" userInput="3" stepKey="checkQuantity10"/> + + <checkOption selector="{{StorefrontBundledSection.radioButtonOptionTwoProducts('Option Two', '2')}}" stepKey="selectOption1Product1"/> + <seeCheckboxIsChecked selector="{{StorefrontBundledSection.radioButtonOptionTwoProducts('Option Two', '2')}}" stepKey="checkOption1Product1"/> + <fillField selector="{{StorefrontBundledSection.radioButtonOptionTwoQuantity('Option Two')}}" userInput="3" stepKey="fillQuantity11"/> + <seeInField selector="{{StorefrontBundledSection.radioButtonOptionTwoQuantity('Option Two')}}" userInput="3" stepKey="checkQuantity11"/> + + <!--"Checkbox" type option--> + <!--This option does not support user defined quantities--> + <checkOption selector="{{StorefrontBundledSection.checkboxOptionThreeProducts('Option Three', '1')}}" stepKey="selectOption2Product0"/> + <seeCheckboxIsChecked selector="{{StorefrontBundledSection.checkboxOptionThreeProducts('Option Three', '1')}}" stepKey="checkOption2Product0"/> + + <checkOption selector="{{StorefrontBundledSection.checkboxOptionThreeProducts('Option Three', '2')}}" stepKey="selectOption2Product1"/> + <seeCheckboxIsChecked selector="{{StorefrontBundledSection.checkboxOptionThreeProducts('Option Three', '2')}}" stepKey="checkOption2Product1"/> + + <!--"Multi Select" type option--> + <!--This option does not support user defined quantities--> + <selectOption selector="{{StorefrontBundledSection.multiselectOptionFourProducts('Option Four')}}" userInput="$$simpleProduct1.sku$$ +$$$simpleProduct1.price$$.00" stepKey="selectOption3Product0"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.multiselectOptionFourProducts('Option Four')}}" userInput="$$simpleProduct1.sku$$ +$$$simpleProduct1.price$$.00" stepKey="checkOption3Product0"/> + + <selectOption selector="{{StorefrontBundledSection.multiselectOptionFourProducts('Option Four')}}" userInput="$$simpleProduct2.sku$$ +$$$simpleProduct2.price$$.00" stepKey="selectOption3Product1"/> + <seeOptionIsSelected selector="{{StorefrontBundledSection.multiselectOptionFourProducts('Option Four')}}" userInput="$$simpleProduct2.sku$$ +$$$simpleProduct2.price$$.00" stepKey="checkOption3Product1"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml index a50a73c7f6bb4..f94cd83f4e7d7 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontEditBundleProductTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml new file mode 100644 index 0000000000000..ccd6a58223b3c --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGoToDetailsPageWhenAddingToCart"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle products list on Storefront"/> + <title value="Customer should be taken to bundle product details page when clicking “Add to Cart” button"/> + <description value="Customer should be taken to bundle product details page when clicking “Add to Cart” button"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-229"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + <!--Go to bundle product creation page--> + <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage" /> + <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> + + <!--Categories--> + <click selector="{{AdminProductFormBundleSection.categoriesDropDown}}" stepKey="dropDownCategories"/> + <fillField selector="{{AdminProductFormBundleSection.searchForCategory}}" userInput="$$createCategory.name$$" stepKey="searchForCategory"/> + <click selector="{{AdminProductFormBundleSection.selectCategory}}" stepKey="selectCategory"/> + <click selector="{{AdminProductFormBundleSection.categoriesLabel}}" stepKey="clickOnCategoriesLabelToCloseOptions"/> + + <!--Create bundle product--> + <conditionalClick selector="{{AdminProductFormBundleSection.bundleItemsToggle}}" dependentSelector="{{AdminProductFormBundleSection.bundleItemsToggle}}" visible="false" stepKey="conditionallyOpenSectionBundleItems"/> + <click selector="{{AdminProductFormBundleSection.addOption}}" stepKey="clickAddOption"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" stepKey="waitForBundleOptions"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXTitle('0')}}" userInput="{{BundleProduct.optionTitle1}}" stepKey="fillOptionTitle"/> + <selectOption selector="{{AdminProductFormBundleSection.bundleOptionXInputType('0')}}" userInput="{{BundleProduct.optionInputType1}}" stepKey="selectInputType"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="waitForAddProductsToBundle"/> + <click selector="{{AdminProductFormBundleSection.addProductsToOption}}" stepKey="clickAddProductsToOption"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterBundleProductOptions2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectFirstGridRow2"/> + <click selector="{{AdminAddProductsToOptionPanel.addSelectedProducts}}" stepKey="clickAddSelectedBundleProducts"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty2"/> + <actionGroup ref="AncillaryPrepBundleProduct" stepKey="createBundledProductForTwoSimpleProducts"/> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> + + <!--Go to category page--> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageToload"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="cartClickCategory"/> + + <!--Click add to cart--> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!--Check for details page--> + <seeInCurrentUrl url="{{BundleProduct.sku}}" stepKey="seeBundleProductDetailsPage"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml new file mode 100644 index 0000000000000..31a5f9bab7758 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest.xml @@ -0,0 +1,238 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifyDynamicBundleProductPricesForCombinationOfOptionsTest"> + <annotations> + <features value="Bundle"/> + <stories value="View bundle products"/> + <title value="Verify dynamic bundle product prices for combination of options"/> + <description value="Verify prices for various configurations of Dynamic Bundle product"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-43619"/> + <group value="bundle"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + + <!--Create 5 simple product--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">4.99</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">2.89</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">7.33</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">18.25</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"> + <field key="price">10.00</field> + </createData> + + <!--Add special price to simple product--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminProductEditPage.url($$simpleProduct5.id$$)}}" stepKey="openAdminEditPage"/> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"/> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!--Create Bundle product--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + <field key="required">false</field> + </createData> + <createData entity="CheckboxOption" stepKey="createBundleOption1_2"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct3"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct3"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct4"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct4"/> + </createData> + + <!--Create Bundle product 2--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleProduct2"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption2_1"> + <requiredEntity createDataKey="createBundleProduct2"/> + </createData> + <createData entity="RadioButtonsOption" stepKey="createBundleOption2_2"> + <requiredEntity createDataKey="createBundleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct5"> + <requiredEntity createDataKey="createBundleProduct2"/> + <requiredEntity createDataKey="createBundleOption2_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + <field key="qty">2</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct6"> + <requiredEntity createDataKey="createBundleProduct2"/> + <requiredEntity createDataKey="createBundleOption2_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct7"> + <requiredEntity createDataKey="createBundleProduct2"/> + <requiredEntity createDataKey="createBundleOption2_2"/> + <requiredEntity createDataKey="simpleProduct3"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct8"> + <requiredEntity createDataKey="createBundleProduct2"/> + <requiredEntity createDataKey="createBundleOption2_2"/> + <requiredEntity createDataKey="simpleProduct4"/> + <field key="qty">5</field> + </createData> + + <!--Create Bundle product 3--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleProduct3"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <createData entity="MultipleSelectOption" stepKey="createBundleOption3_1"> + <requiredEntity createDataKey="createBundleProduct3"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="createBundleOption3_2"> + <requiredEntity createDataKey="createBundleProduct3"/> + <field key="required">false</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct9"> + <requiredEntity createDataKey="createBundleProduct3"/> + <requiredEntity createDataKey="createBundleOption3_1"/> + <requiredEntity createDataKey="simpleProduct4"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct10"> + <requiredEntity createDataKey="createBundleProduct3"/> + <requiredEntity createDataKey="createBundleOption3_1"/> + <requiredEntity createDataKey="simpleProduct5"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct11"> + <requiredEntity createDataKey="createBundleProduct3"/> + <requiredEntity createDataKey="createBundleOption3_2"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct12"> + <requiredEntity createDataKey="createBundleProduct3"/> + <requiredEntity createDataKey="createBundleOption3_2"/> + <requiredEntity createDataKey="simpleProduct3"/> + </createData> + + <!-- navigate to the tax configuration page --> + <amOnPage url="{{AdminTaxConfigurationPage.url}}" stepKey="goToAdminTaxPage"/> + <waitForPageLoad stepKey="waitForTaxConfigLoad"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationSettings}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationSettingsOpened}}" visible="false" stepKey="openCalculationSettingsTab"/> + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationAlgorithmInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationAlgorithmDisabled}}" visible="true" stepKey="clickCalculationMethodBasedCheckBox"/> + <selectOption userInput="Total" selector="{{AdminConfigureTaxSection.taxCalculationAlgorithm}}" stepKey="fillCalculationMethodBased"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationBasedInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationBasedDisabled}}" visible="true" stepKey="clickTaxCalculationBasedCheckBox"/> + <selectOption userInput="Shipping Origin" selector="{{AdminConfigureTaxSection.taxCalculationBased}}" stepKey="fillTaxCalculationBased"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationPricesInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationPricesDisabled}}" visible="true" stepKey="clickCalculationPricesCheckBox"/> + <selectOption userInput="Excluding Tax" selector="{{AdminConfigureTaxSection.taxCalculationPrices}}" stepKey="clickCalculationPrices"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxPriceDisplaySettings}}" dependentSelector="{{AdminConfigureTaxSection.taxPriceDisplaySettingsOpened}}" visible="false" stepKey="openPriceDisplaySettings"/> + <conditionalClick selector="{{AdminConfigureTaxSection.taxDisplayProductPricesInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxDisplayProductPricesDisabled}}" visible="true" stepKey="clickDisplayProductPricesCheckBox"/> + <selectOption userInput="Excluding Tax" selector="{{AdminConfigureTaxSection.taxDisplayProductPrices}}" stepKey="clickDisplayProductPrices"/> + + <!-- Save the settings --> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveTaxOptions"/> + <waitForPageLoad stepKey="waitForTaxSaved"/> + <see userInput="You saved the configuration." selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccess"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!-- navigate to the tax configuration page --> + <amOnPage url="{{AdminTaxConfigurationPage.url}}" stepKey="goToAdminTaxPage"/> + <waitForPageLoad stepKey="waitForTaxConfigLoad"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationSettings}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationSettingsOpened}}" visible="false" stepKey="openCalculationSettingsTab"/> + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationAlgorithmInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationAlgorithmDisabled}}" visible="true" stepKey="clickCalculationMethodBasedCheckBox"/> + <selectOption userInput="Total" selector="{{AdminConfigureTaxSection.taxCalculationAlgorithm}}" stepKey="fillCalculationMethodBased"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationBasedInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationBasedDisabled}}" visible="true" stepKey="clickTaxCalculationBasedCheckBox"/> + <selectOption userInput="Shipping Address" selector="{{AdminConfigureTaxSection.taxCalculationBased}}" stepKey="fillTaxCalculationBased"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationPricesInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationPricesDisabled}}" visible="true" stepKey="clickCalculationPricesCheckBox"/> + <selectOption userInput="Excluding Tax" selector="{{AdminConfigureTaxSection.taxCalculationPrices}}" stepKey="clickCalculationPrices"/> + + <conditionalClick selector="{{AdminConfigureTaxSection.taxPriceDisplaySettings}}" dependentSelector="{{AdminConfigureTaxSection.taxPriceDisplaySettingsOpened}}" visible="false" stepKey="openPriceDisplaySettings"/> + <conditionalClick selector="{{AdminConfigureTaxSection.taxDisplayProductPricesInherit}}" dependentSelector="{{AdminConfigureTaxSection.taxDisplayProductPricesDisabled}}" visible="true" stepKey="clickDisplayProductPricesCheckBox"/> + <selectOption userInput="Excluding Tax" selector="{{AdminConfigureTaxSection.taxDisplayProductPrices}}" stepKey="clickDisplayProductPrices"/> + + <!-- Save the settings --> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveTaxOptions"/> + <waitForPageLoad stepKey="waitForTaxSaved"/> + <see userInput="You saved the configuration." selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccess"/> + + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteSubCategory1"/> + + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteSimpleProduct5"/> + + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createBundleProduct2" stepKey="deleteBundleProduct2"/> + <deleteData createDataKey="createBundleProduct3" stepKey="deleteBundleProduct3"/> + </after> + + <!-- Go to storefront category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$createSubCategory.name$$)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + + <see userInput="From $7.33" selector="{{StorefrontCategoryProductSection.priceFromByProductId($$createBundleProduct.id$$)}}" stepKey="seePriceFromInCategoryBundle1"/> + <see userInput="To $33.46" selector="{{StorefrontCategoryProductSection.priceToByProductId($$createBundleProduct.id$$)}}" stepKey="seePriceToInCategoryBundle1"/> + + <see userInput="From $10.22" selector="{{StorefrontCategoryProductSection.priceFromByProductId($$createBundleProduct2.id$$)}}" stepKey="seePriceFromInCategoryBundle2"/> + <see userInput="To $101.23" selector="{{StorefrontCategoryProductSection.priceToByProductId($$createBundleProduct2.id$$)}}" stepKey="seePriceToInCategoryBundle2"/> + + <see userInput="From $8.00 Regular Price $10.00" selector="{{StorefrontCategoryProductSection.priceFromByProductId($$createBundleProduct3.id$$)}}" stepKey="seePriceFromInCategoryBundle3"/> + <see userInput="To $33.58 Regular Price $35.58" selector="{{StorefrontCategoryProductSection.priceToByProductId($$createBundleProduct3.id$$)}}" stepKey="seePriceToInCategoryBundle3"/> + + <!-- Go to storefront product pages --> + <amOnPage url="{{StorefrontProductPage.url($$createBundleProduct.custom_attributes[url_key]$$)}}" stepKey="onPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="From $7.33" selector="{{StorefrontProductInfoMainSection.priceFrom}}" stepKey="seePriceFromBundle1"/> + <see userInput="To $33.46" selector="{{StorefrontProductInfoMainSection.priceTo}}" stepKey="seePriceToBundle1"/> + + <amOnPage url="{{StorefrontProductPage.url($$createBundleProduct2.custom_attributes[url_key]$$)}}" stepKey="onPageBundle2"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <see userInput="From $10.22" selector="{{StorefrontProductInfoMainSection.priceFrom}}" stepKey="seePriceFromBundle2"/> + <see userInput="To $101.23" selector="{{StorefrontProductInfoMainSection.priceTo}}" stepKey="seePriceToBundle2"/> + + <amOnPage url="{{StorefrontProductPage.url($$createBundleProduct3.custom_attributes[url_key]$$)}}" stepKey="onPageBundle3"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <see userInput="From $8.00 Regular Price $10.00" selector="{{StorefrontProductInfoMainSection.priceFrom}}" stepKey="seePriceFromBundle3"/> + <see userInput="To $33.58 Regular Price $35.58" selector="{{StorefrontProductInfoMainSection.priceTo}}" stepKey="seePriceToBundle3"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/composer.json b/app/code/Magento/Bundle/Test/Mftf/composer.json deleted file mode 100644 index 1e379d1bacd44..0000000000000 --- a/app/code/Magento/Bundle/Test/Mftf/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "magento/functional-test-module-bundle", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/functional-test-module-bundle-sample-data": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php b/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php deleted file mode 100644 index e595f9a47f060..0000000000000 --- a/app/code/Magento/Bundle/Test/Unit/Model/ResourceModel/Selection/CollectionTest.php +++ /dev/null @@ -1,156 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Bundle\Test\Unit\Model\ResourceModel\Selection; - -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Store\Api\Data\StoreInterface; -use Magento\Framework\Validator\UniversalFactory; -use Magento\Eav\Model\Entity\AbstractEntity; -use Magento\Framework\DB\Adapter\AdapterInterface; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; -use Magento\Framework\DB\Select; - -/** - * Class CollectionTest. - */ -class CollectionTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $storeManager; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $store; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $universalFactory; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $entity; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $adapter; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $select; - - /** - * @var \Magento\Bundle\Model\ResourceModel\Selection\Collection - */ - private $model; - - protected function setUp() - { - $objectManager = new ObjectManager($this); - $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->store = $this->getMockBuilder(StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->universalFactory = $this->getMockBuilder(UniversalFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->entity = $this->getMockBuilder(AbstractEntity::class) - ->disableOriginalConstructor() - ->getMock(); - $this->adapter = $this->getMockBuilder(AdapterInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->select = $this->getMockBuilder(Select::class) - ->disableOriginalConstructor() - ->getMock(); - $factory = $this->getMockBuilder(ProductLimitationFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - - $this->storeManager->expects($this->any()) - ->method('getStore') - ->willReturn($this->store); - $this->store->expects($this->any()) - ->method('getId') - ->willReturn(1); - $this->universalFactory->expects($this->any()) - ->method('create') - ->willReturn($this->entity); - $this->entity->expects($this->any()) - ->method('getConnection') - ->willReturn($this->adapter); - $this->entity->expects($this->any()) - ->method('getDefaultAttributes') - ->willReturn([]); - $this->adapter->expects($this->any()) - ->method('select') - ->willReturn($this->select); - - $this->model = $objectManager->getObject( - \Magento\Bundle\Model\ResourceModel\Selection\Collection::class, - [ - 'storeManager' => $this->storeManager, - 'universalFactory' => $this->universalFactory, - 'productLimitationFactory' => $factory - ] - ); - } - - public function testAddQuantityFilter() - { - $statusTableName = 'cataloginventory_stock_status'; - $itemTableName = 'cataloginventory_stock_item'; - $this->entity->expects($this->exactly(2)) - ->method('getTable') - ->willReturnMap([ - ['cataloginventory_stock_item', $itemTableName], - ['cataloginventory_stock_status', $statusTableName], - ]); - $this->select->expects($this->exactly(2)) - ->method('joinInner') - ->withConsecutive( - [ - ['stock' => $statusTableName], - 'selection.product_id = stock.product_id', - [], - ], - [ - ['stock_item' => $itemTableName], - 'selection.product_id = stock_item.product_id', - [], - ] - )->willReturnSelf(); - $this->select - ->expects($this->exactly(2)) - ->method('where') - ->withConsecutive( - [ - '(' - . 'selection.selection_can_change_qty > 0' - . ' or ' - . 'selection.selection_qty <= stock.qty' - . ' or ' - .'stock_item.manage_stock = 0' - . ')', - ], - [ - 'stock.stock_status = 1', - ] - )->willReturnSelf(); - - $this->assertEquals($this->model, $this->model->addQuantityFilter()); - } -} diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php index d35619bb043a0..9f35251b3f926 100644 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/BundleSelectionPriceTest.php @@ -121,7 +121,7 @@ protected function setupSelectionPrice($useRegularPrice = false) } /** - * test fro method getValue with dynamic productType + * Test for method getValue with dynamic productType * * @param bool $useRegularPrice * @dataProvider useRegularPriceDataProvider diff --git a/app/code/Magento/Bundle/etc/db_schema_whitelist.json b/app/code/Magento/Bundle/etc/db_schema_whitelist.json index efb535d50caa3..2834d707cae0f 100644 --- a/app/code/Magento/Bundle/etc/db_schema_whitelist.json +++ b/app/code/Magento/Bundle/etc/db_schema_whitelist.json @@ -1,212 +1,212 @@ { - "catalog_product_bundle_option": { - "column": { - "option_id": true, - "parent_id": true, - "required": true, - "position": true, - "type": true - }, - "index": { - "CATALOG_PRODUCT_BUNDLE_OPTION_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_BNDL_OPT_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true - } - }, - "catalog_product_bundle_option_value": { - "column": { - "value_id": true, - "option_id": true, - "store_id": true, - "title": true, - "parent_product_id": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_BNDL_OPT_VAL_OPT_ID_CAT_PRD_BNDL_OPT_OPT_ID": true, - "CAT_PRD_BNDL_OPT_VAL_OPT_ID_PARENT_PRD_ID_STORE_ID": true, - "CATALOG_PRODUCT_BUNDLE_OPTION_VALUE_OPTION_ID_STORE_ID": true - } - }, - "catalog_product_bundle_selection": { - "column": { - "selection_id": true, - "option_id": true, - "parent_product_id": true, - "product_id": true, - "position": true, - "is_default": true, - "selection_price_type": true, - "selection_price_value": true, - "selection_qty": true, - "selection_can_change_qty": true - }, - "index": { - "CATALOG_PRODUCT_BUNDLE_SELECTION_OPTION_ID": true, - "CATALOG_PRODUCT_BUNDLE_SELECTION_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_BNDL_SELECTION_OPT_ID_CAT_PRD_BNDL_OPT_OPT_ID": true, - "CAT_PRD_BNDL_SELECTION_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_BNDL_SELECTION_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "catalog_product_bundle_selection_price": { - "column": { - "selection_id": true, - "website_id": true, - "selection_price_type": true, - "selection_price_value": true, - "parent_product_id": true - }, - "index": { - "CATALOG_PRODUCT_BUNDLE_SELECTION_PRICE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "PK_CATALOG_PRODUCT_BUNDLE_SELECTION_PRICE": true, - "CAT_PRD_BNDL_SELECTION_PRICE_WS_ID_STORE_WS_WS_ID": true, - "FK_DCF37523AA05D770A70AA4ED7C2616E4": true, - "DCF37523AA05D770A70AA4ED7C2616E4": true - } - }, - "catalog_product_bundle_price_index": { - "column": { - "entity_id": true, - "website_id": true, - "customer_group_id": true, - "min_price": true, - "max_price": true - }, - "index": { - "CATALOG_PRODUCT_BUNDLE_PRICE_INDEX_WEBSITE_ID": true, - "CATALOG_PRODUCT_BUNDLE_PRICE_INDEX_CUSTOMER_GROUP_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_BNDL_PRICE_IDX_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "CAT_PRD_BNDL_PRICE_IDX_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_BNDL_PRICE_IDX_WS_ID_STORE_WS_WS_ID": true, - "CAT_PRD_BNDL_PRICE_IDX_ENTT_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "catalog_product_bundle_stock_index": { - "column": { - "entity_id": true, - "website_id": true, - "stock_id": true, - "option_id": true, - "stock_status": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price_type": true, - "special_price": true, - "tier_percent": true, - "orig_price": true, - "price": true, - "min_price": true, - "max_price": true, - "tier_price": true, - "base_tier": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price_type": true, - "special_price": true, - "tier_percent": true, - "orig_price": true, - "price": true, - "min_price": true, - "max_price": true, - "tier_price": true, - "base_tier": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_sel_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "selection_id": true, - "group_type": true, - "is_required": true, - "price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_sel_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "selection_id": true, - "group_type": true, - "is_required": true, - "price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_opt_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "min_price": true, - "alt_price": true, - "max_price": true, - "tier_price": true, - "alt_tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_bundle_opt_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "min_price": true, - "alt_price": true, - "max_price": true, - "tier_price": true, - "alt_tier_price": true - }, - "constraint": { - "PRIMARY": true + "catalog_product_bundle_option": { + "column": { + "option_id": true, + "parent_id": true, + "required": true, + "position": true, + "type": true + }, + "index": { + "CATALOG_PRODUCT_BUNDLE_OPTION_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_BNDL_OPT_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true + } + }, + "catalog_product_bundle_option_value": { + "column": { + "value_id": true, + "option_id": true, + "store_id": true, + "title": true, + "parent_product_id": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_BNDL_OPT_VAL_OPT_ID_CAT_PRD_BNDL_OPT_OPT_ID": true, + "CAT_PRD_BNDL_OPT_VAL_OPT_ID_PARENT_PRD_ID_STORE_ID": true, + "CATALOG_PRODUCT_BUNDLE_OPTION_VALUE_OPTION_ID_STORE_ID": true + } + }, + "catalog_product_bundle_selection": { + "column": { + "selection_id": true, + "option_id": true, + "parent_product_id": true, + "product_id": true, + "position": true, + "is_default": true, + "selection_price_type": true, + "selection_price_value": true, + "selection_qty": true, + "selection_can_change_qty": true + }, + "index": { + "CATALOG_PRODUCT_BUNDLE_SELECTION_OPTION_ID": true, + "CATALOG_PRODUCT_BUNDLE_SELECTION_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_BNDL_SELECTION_OPT_ID_CAT_PRD_BNDL_OPT_OPT_ID": true, + "CAT_PRD_BNDL_SELECTION_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_BNDL_SELECTION_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } + }, + "catalog_product_bundle_selection_price": { + "column": { + "selection_id": true, + "website_id": true, + "selection_price_type": true, + "selection_price_value": true, + "parent_product_id": true + }, + "index": { + "CATALOG_PRODUCT_BUNDLE_SELECTION_PRICE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "PK_CATALOG_PRODUCT_BUNDLE_SELECTION_PRICE": true, + "CAT_PRD_BNDL_SELECTION_PRICE_WS_ID_STORE_WS_WS_ID": true, + "FK_DCF37523AA05D770A70AA4ED7C2616E4": true, + "DCF37523AA05D770A70AA4ED7C2616E4": true + } + }, + "catalog_product_bundle_price_index": { + "column": { + "entity_id": true, + "website_id": true, + "customer_group_id": true, + "min_price": true, + "max_price": true + }, + "index": { + "CATALOG_PRODUCT_BUNDLE_PRICE_INDEX_WEBSITE_ID": true, + "CATALOG_PRODUCT_BUNDLE_PRICE_INDEX_CUSTOMER_GROUP_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_BNDL_PRICE_IDX_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "CAT_PRD_BNDL_PRICE_IDX_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_BNDL_PRICE_IDX_WS_ID_STORE_WS_WS_ID": true, + "CAT_PRD_BNDL_PRICE_IDX_ENTT_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } + }, + "catalog_product_bundle_stock_index": { + "column": { + "entity_id": true, + "website_id": true, + "stock_id": true, + "option_id": true, + "stock_status": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price_type": true, + "special_price": true, + "tier_percent": true, + "orig_price": true, + "price": true, + "min_price": true, + "max_price": true, + "tier_price": true, + "base_tier": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price_type": true, + "special_price": true, + "tier_percent": true, + "orig_price": true, + "price": true, + "min_price": true, + "max_price": true, + "tier_price": true, + "base_tier": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_sel_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "selection_id": true, + "group_type": true, + "is_required": true, + "price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_sel_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "selection_id": true, + "group_type": true, + "is_required": true, + "price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_opt_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "min_price": true, + "alt_price": true, + "max_price": true, + "tier_price": true, + "alt_tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_bundle_opt_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "min_price": true, + "alt_price": true, + "max_price": true, + "tier_price": true, + "alt_tier_price": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/BundleGraphQl/Test/Mftf/composer.json b/app/code/Magento/BundleGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 33dffdc85a364..0000000000000 --- a/app/code/Magento/BundleGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-bundle-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/BundleImportExport/Test/Mftf/composer.json b/app/code/Magento/BundleImportExport/Test/Mftf/composer.json deleted file mode 100644 index 2210ef5248159..0000000000000 --- a/app/code/Magento/BundleImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-bundle-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json b/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json deleted file mode 100644 index 0a6ffcf2f4661..0000000000000 --- a/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-cache-invalidate", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-page-cache": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php index e496c36f4de75..91f3a785df36b 100644 --- a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php +++ b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Captcha\Model\Customer\Plugin; use Magento\Captcha\Helper\Data as CaptchaHelper; @@ -81,27 +82,37 @@ public function aroundExecute( if ($content) { $loginParams = $this->serializer->unserialize($content); } - $username = isset($loginParams['username']) ? $loginParams['username'] : null; - $captchaString = isset($loginParams[$captchaInputName]) ? $loginParams[$captchaInputName] : null; - $loginFormId = isset($loginParams[$captchaFormIdField]) ? $loginParams[$captchaFormIdField] : null; + $username = $loginParams['username'] ?? null; + $captchaString = $loginParams[$captchaInputName] ?? null; + $loginFormId = $loginParams[$captchaFormIdField] ?? null; - foreach ($this->formIds as $formId) { - $captchaModel = $this->helper->getCaptcha($formId); - if ($captchaModel->isRequired($username) && !in_array($loginFormId, $this->formIds)) { - $resultJson = $this->resultJsonFactory->create(); - return $resultJson->setData(['errors' => true, 'message' => __('Provided form does not exist')]); - } + if (!in_array($loginFormId, $this->formIds) && $this->helper->getCaptcha($loginFormId)->isRequired($username)) { + return $this->returnJsonError(__('Provided form does not exist')); + } - if ($formId == $loginFormId) { - $captchaModel->logAttempt($username); - if (!$captchaModel->isCorrect($captchaString)) { - $this->sessionManager->setUsername($username); - /** @var \Magento\Framework\Controller\Result\Json $resultJson */ - $resultJson = $this->resultJsonFactory->create(); - return $resultJson->setData(['errors' => true, 'message' => __('Incorrect CAPTCHA')]); + foreach ($this->formIds as $formId) { + if ($formId === $loginFormId) { + $captchaModel = $this->helper->getCaptcha($formId); + if ($captchaModel->isRequired($username)) { + $captchaModel->logAttempt($username); + if (!$captchaModel->isCorrect($captchaString)) { + $this->sessionManager->setUsername($username); + return $this->returnJsonError(__('Incorrect CAPTCHA')); + } } } } return $proceed(); } + + /** + * + * @param \Magento\Framework\Phrase $phrase + * @return \Magento\Framework\Controller\Result\Json + */ + private function returnJsonError(\Magento\Framework\Phrase $phrase): \Magento\Framework\Controller\Result\Json + { + $resultJson = $this->resultJsonFactory->create(); + return $resultJson->setData(['errors' => true, 'message' => $phrase]); + } } diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php index e5c72ba1ae82e..cf6690df5c85d 100644 --- a/app/code/Magento/Captcha/Model/DefaultModel.php +++ b/app/code/Magento/Captcha/Model/DefaultModel.php @@ -5,6 +5,8 @@ */ namespace Magento\Captcha\Model; +use Magento\Captcha\Helper\Data; + /** * Implementation of \Zend\Captcha\Image * @@ -29,7 +31,7 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model const DEFAULT_WORD_LENGTH_TO = 5; /** - * @var \Magento\Captcha\Helper\Data + * @var Data * @since 100.2.0 */ protected $captchaData; @@ -125,8 +127,8 @@ public function getBlockName() */ public function isRequired($login = null) { - if ($this->isUserAuth() - && !$this->isShownToLoggedInUser() + if (($this->isUserAuth() + && !$this->isShownToLoggedInUser()) || !$this->isEnabled() || !in_array( $this->formId, @@ -431,12 +433,14 @@ public function getWordLen() */ private function isShowAlways() { - if ((string)$this->captchaData->getConfig('mode') == \Magento\Captcha\Helper\Data::MODE_ALWAYS) { + $captchaMode = (string)$this->captchaData->getConfig('mode'); + + if ($captchaMode === Data::MODE_ALWAYS) { return true; } - if ((string)$this->captchaData->getConfig('mode') == \Magento\Captcha\Helper\Data::MODE_AFTER_FAIL - && $this->getAllowedAttemptsForSameLogin() == 0 + if ($captchaMode === Data::MODE_AFTER_FAIL + && $this->getAllowedAttemptsForSameLogin() === 0 ) { return true; } diff --git a/app/code/Magento/Captcha/Observer/CheckGuestCheckoutObserver.php b/app/code/Magento/Captcha/Observer/CheckGuestCheckoutObserver.php deleted file mode 100644 index 40c215ec218a1..0000000000000 --- a/app/code/Magento/Captcha/Observer/CheckGuestCheckoutObserver.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Captcha\Observer; - -use Magento\Framework\Event\ObserverInterface; - -class CheckGuestCheckoutObserver implements ObserverInterface -{ - /** - * @var \Magento\Captcha\Helper\Data - */ - protected $_helper; - - /** - * @var \Magento\Framework\App\ActionFlag - */ - protected $_actionFlag; - - /** - * @var CaptchaStringResolver - */ - protected $captchaStringResolver; - - /** - * @var \Magento\Checkout\Model\Type\Onepage - */ - protected $_typeOnepage; - - /** - * @var \Magento\Framework\Json\Helper\Data - */ - protected $jsonHelper; - - /** - * @param \Magento\Captcha\Helper\Data $helper - * @param \Magento\Framework\App\ActionFlag $actionFlag - * @param CaptchaStringResolver $captchaStringResolver - * @param \Magento\Checkout\Model\Type\Onepage $typeOnepage - * @param \Magento\Framework\Json\Helper\Data $jsonHelper - */ - public function __construct( - \Magento\Captcha\Helper\Data $helper, - \Magento\Framework\App\ActionFlag $actionFlag, - CaptchaStringResolver $captchaStringResolver, - \Magento\Checkout\Model\Type\Onepage $typeOnepage, - \Magento\Framework\Json\Helper\Data $jsonHelper - ) { - $this->_helper = $helper; - $this->_actionFlag = $actionFlag; - $this->captchaStringResolver = $captchaStringResolver; - $this->_typeOnepage = $typeOnepage; - $this->jsonHelper = $jsonHelper; - } - - /** - * Check Captcha On Checkout as Guest Page - * - * @param \Magento\Framework\Event\Observer $observer - * @return $this - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - $formId = 'guest_checkout'; - $captchaModel = $this->_helper->getCaptcha($formId); - $checkoutMethod = $this->_typeOnepage->getQuote()->getCheckoutMethod(); - if ($checkoutMethod == \Magento\Checkout\Model\Type\Onepage::METHOD_GUEST) { - if ($captchaModel->isRequired()) { - $controller = $observer->getControllerAction(); - if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId)) - ) { - $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); - $result = ['error' => 1, 'message' => __('Incorrect CAPTCHA')]; - $controller->getResponse()->representJson($this->jsonHelper->jsonEncode($result)); - } - } - } - - return $this; - } -} diff --git a/app/code/Magento/Captcha/Observer/CheckRegisterCheckoutObserver.php b/app/code/Magento/Captcha/Observer/CheckRegisterCheckoutObserver.php deleted file mode 100644 index 3bf2ac38debee..0000000000000 --- a/app/code/Magento/Captcha/Observer/CheckRegisterCheckoutObserver.php +++ /dev/null @@ -1,83 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Captcha\Observer; - -use Magento\Framework\Event\ObserverInterface; - -class CheckRegisterCheckoutObserver implements ObserverInterface -{ - /** - * @var \Magento\Captcha\Helper\Data - */ - protected $_helper; - - /** - * @var \Magento\Framework\App\ActionFlag - */ - protected $_actionFlag; - - /** - * @var CaptchaStringResolver - */ - protected $captchaStringResolver; - - /** - * @var \Magento\Checkout\Model\Type\Onepage - */ - protected $_typeOnepage; - - /** - * @var \Magento\Framework\Json\Helper\Data - */ - protected $jsonHelper; - - /** - * @param \Magento\Captcha\Helper\Data $helper - * @param \Magento\Framework\App\ActionFlag $actionFlag - * @param CaptchaStringResolver $captchaStringResolver - * @param \Magento\Checkout\Model\Type\Onepage $typeOnepage - * @param \Magento\Framework\Json\Helper\Data $jsonHelper - */ - public function __construct( - \Magento\Captcha\Helper\Data $helper, - \Magento\Framework\App\ActionFlag $actionFlag, - CaptchaStringResolver $captchaStringResolver, - \Magento\Checkout\Model\Type\Onepage $typeOnepage, - \Magento\Framework\Json\Helper\Data $jsonHelper - ) { - $this->_helper = $helper; - $this->_actionFlag = $actionFlag; - $this->captchaStringResolver = $captchaStringResolver; - $this->_typeOnepage = $typeOnepage; - $this->jsonHelper = $jsonHelper; - } - - /** - * Check Captcha On Checkout Register Page - * - * @param \Magento\Framework\Event\Observer $observer - * @return $this - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - $formId = 'register_during_checkout'; - $captchaModel = $this->_helper->getCaptcha($formId); - $checkoutMethod = $this->_typeOnepage->getQuote()->getCheckoutMethod(); - if ($checkoutMethod == \Magento\Checkout\Model\Type\Onepage::METHOD_REGISTER) { - if ($captchaModel->isRequired()) { - $controller = $observer->getControllerAction(); - if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId)) - ) { - $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); - $result = ['error' => 1, 'message' => __('Incorrect CAPTCHA')]; - $controller->getResponse()->representJson($this->jsonHelper->jsonEncode($result)); - } - } - } - - return $this; - } -} diff --git a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php index 402fc028c5ad0..2de93dcf6b59b 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php @@ -69,18 +69,17 @@ public function execute(\Magento\Framework\Event\Observer $observer) $controller = $observer->getControllerAction(); $email = (string)$observer->getControllerAction()->getRequest()->getParam('email'); $params = $observer->getControllerAction()->getRequest()->getParams(); - if (!empty($email) && !empty($params)) { - if ($captchaModel->isRequired()) { - if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId)) - ) { - $this->_session->setEmail((string)$controller->getRequest()->getPost('email')); - $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); - $this->messageManager->addError(__('Incorrect CAPTCHA')); - $controller->getResponse()->setRedirect( - $controller->getUrl('*/*/forgotpassword', ['_nosecret' => true]) - ); - } - } + if (!empty($email) + && !empty($params) + && $captchaModel->isRequired() + && !$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId)) + ) { + $this->_session->setEmail((string)$controller->getRequest()->getPost('email')); + $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); + $this->messageManager->addError(__('Incorrect CAPTCHA')); + $controller->getResponse()->setRedirect( + $controller->getUrl('*/*/forgotpassword', ['_nosecret' => true]) + ); } return $this; diff --git a/app/code/Magento/Captcha/Observer/CheckUserLoginBackendObserver.php b/app/code/Magento/Captcha/Observer/CheckUserLoginBackendObserver.php index 8cc907d7bd12b..924514cd48c5d 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserLoginBackendObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserLoginBackendObserver.php @@ -52,11 +52,11 @@ public function execute(\Magento\Framework\Event\Observer $observer) $formId = 'backend_login'; $captchaModel = $this->_helper->getCaptcha($formId); $login = $observer->getEvent()->getUsername(); - if ($captchaModel->isRequired($login)) { - if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($this->_request, $formId))) { - $captchaModel->logAttempt($login); - throw new PluginAuthenticationException(__('Incorrect CAPTCHA.')); - } + if ($captchaModel->isRequired($login) + && !$captchaModel->isCorrect($this->captchaStringResolver->resolve($this->_request, $formId)) + ) { + $captchaModel->logAttempt($login); + throw new PluginAuthenticationException(__('Incorrect CAPTCHA.')); } $captchaModel->logAttempt($login); diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml new file mode 100644 index 0000000000000..71a876bbbcdbf --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CaptchaFormsDisplayingActionGroup"> + <click selector="{{CaptchaFormsDisplayingSection.store}}" stepKey="ClickToGoStores"/> + <waitForPageLoad stepKey="waitForStoresLoaded"/> + <click selector="{{CaptchaFormsDisplayingSection.config}}" stepKey="ClickToGoConfiguration"/> + <waitForPageLoad stepKey="waitForConfigurationsLoaded"/> + <scrollTo selector="{{CaptchaFormsDisplayingSection.customer}}" stepKey="ScrollToCustomers"/> + <click selector="{{CaptchaFormsDisplayingSection.customer}}" stepKey="ClickToCustomers"/> + <waitForPageLoad stepKey="waitForCustomerConfigurationsLoaded"/> + <click selector="{{CaptchaFormsDisplayingSection.customerConfig}}" stepKey="ClickToGoCustomerConfiguration"/> + <scrollTo selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="scrollToCaptcha"/> + <conditionalClick selector="{{CaptchaFormsDisplayingSection.captcha}}" dependentSelector="{{CaptchaFormsDisplayingSection.dependent}}" visible="false" stepKey="ClickToOpenCaptcha"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml new file mode 100644 index 0000000000000..9db8110c0f64b --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CaptchaData"> + <data key="createUser">Create user</data> + <data key="login">Login</data> + <data key="passwd">Forgot password</data> + <data key="contactUs">Contact Us</data> + <data key="changePasswd">Change password</data> + <data key="register">Register during Checkout</data> + <data key="checkoutAsGuest">Check Out as Guest</data> + </entity> +</entities> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml new file mode 100644 index 0000000000000..4c974e6fced05 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/CaptchaFormsDisplayingSection.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CaptchaFormsDisplayingSection"> + <element name="store" type="button" selector="#menu-magento-backend-stores"/> + <element name="config" type="button" selector="//li[@data-ui-id='menu-magento-config-system-config']//span"/> + <element name="customer" type="button" selector="//div[@class='admin__page-nav-title title _collapsible']//strong[text()='Customers']"/> + <element name="customerConfig" type="text" selector="//span[text()='Customer Configuration']"/> + <element name="captcha" type="button" selector="#customer_captcha-head"/> + <element name="dependent" type="button" selector="//a[@id='customer_captcha-head' and @class='open']"/> + <element name="forms" type="multiselect" selector="#customer_captcha_forms"/> + <element name="createUser" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='user_create']"/> + <element name="forgotpassword" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='user_forgotpassword']"/> + <element name="userLogin" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='user_login']"/> + <element name="guestCheckout" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='guest_checkout']"/> + <element name="register" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='register_during_checkout']"/> + <element name="userEdit" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='user_edit']"/> + <element name="contactUs" type="multiselect" selector="//select[@id='customer_captcha_forms']/option[@value='contact_us']"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml new file mode 100644 index 0000000000000..8f764899706ac --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="CaptchaFormsDisplayingTest"> + <annotations> + <features value="Captcha"/> + <stories value="MAGETWO-91552 - [github] CAPTCHA doesn't show when check out as guest"/> + <title value="Captcha forms displaying"/> + <description value="Captcha forms displaying"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93941"/> + <group value="captcha"/> + </annotations> + + <!--Login as admin--> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Go to Captcha--> + <actionGroup ref="CaptchaFormsDisplayingActionGroup" stepKey="CaptchaFormsDisplayingActionGroup"/> + <waitForPageLoad stepKey="WaitForPageLoaded"/> + <!--Verify fields removed--> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.forms}}" stepKey="formItems"/> + <assertNotContains stepKey="checkoutAsGuest"> + <expectedResult type="string">{{CaptchaData.checkoutAsGuest}}</expectedResult> + <actualResult type="variable">$formItems</actualResult> + </assertNotContains> + <assertNotContains stepKey="register"> + <expectedResult type="string">{{CaptchaData.register}}</expectedResult> + <actualResult type="variable">$formItems</actualResult> + </assertNotContains> + <!--Verify fields existence--> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.createUser}}" stepKey="createUser"/> + <assertEquals stepKey="CreateUserFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.createUser}}</expectedResult> + <actualResult type="variable">$createUser</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.userLogin}}" stepKey="login"/> + <assertEquals stepKey="LoginFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.login}}</expectedResult> + <actualResult type="variable">login</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.forgotpassword}}" stepKey="forgotpassword"/> + <assertEquals stepKey="PasswordFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.passwd}}</expectedResult> + <actualResult type="variable">$forgotpassword</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.contactUs}}" stepKey="contactUs"/> + <assertEquals stepKey="contactUsFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.contactUs}}</expectedResult> + <actualResult type="variable">$contactUs</actualResult> + </assertEquals> + <grabTextFrom selector="{{CaptchaFormsDisplayingSection.userEdit}}" stepKey="userEdit"/> + <assertEquals stepKey="userEditFieldIsPresent"> + <expectedResult type="string">{{CaptchaData.changePasswd}}</expectedResult> + <actualResult type="variable">$userEdit</actualResult> + </assertEquals> + + <!--Roll back configuration--> + <scrollToTopOfPage stepKey="ScrollToTop"/> + <click selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="ClickToCloseCaptcha"/> + + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/composer.json b/app/code/Magento/Captcha/Test/Mftf/composer.json deleted file mode 100644 index 95c3aa5f76078..0000000000000 --- a/app/code/Magento/Captcha/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-captcha", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php index be9574fff2cfa..ec2a49f3fc566 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php @@ -3,22 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Captcha\Test\Unit\Model\Customer\Plugin; class AjaxLoginTest extends \PHPUnit\Framework\TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Checkout\Model\Session */ protected $sessionManagerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Captcha\Helper\Data */ protected $captchaHelperMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Controller\Result\JsonFactory */ protected $jsonFactoryMock; @@ -38,12 +39,12 @@ class AjaxLoginTest extends \PHPUnit\Framework\TestCase protected $requestMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Customer\Controller\Ajax\Login */ protected $loginControllerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Serialize\Serializer\Json */ protected $serializerMock; @@ -57,6 +58,9 @@ class AjaxLoginTest extends \PHPUnit\Framework\TestCase */ protected $model; + /** + * @inheritdoc + */ protected function setUp() { $this->sessionManagerMock = $this->createPartialMock(\Magento\Checkout\Model\Session::class, ['setUsername']); @@ -72,8 +76,12 @@ protected function setUp() $this->loginControllerMock->expects($this->any())->method('getRequest') ->will($this->returnValue($this->requestMock)); - $this->captchaHelperMock->expects($this->once())->method('getCaptcha') - ->with('user_login')->will($this->returnValue($this->captchaMock)); + + $this->captchaHelperMock + ->expects($this->exactly(1)) + ->method('getCaptcha') + ->will($this->returnValue($this->captchaMock)); + $this->formIds = ['user_login']; $this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); @@ -86,6 +94,9 @@ protected function setUp() ); } + /** + * Test aroundExecute. + */ public function testAroundExecute() { $username = 'name'; @@ -103,14 +114,24 @@ public function testAroundExecute() $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); $this->captchaMock->expects($this->once())->method('isCorrect')->with($captchaString) ->will($this->returnValue(true)); - $this->serializerMock->expects(($this->once()))->method('unserialize')->will($this->returnValue($requestData)); + $this->serializerMock->expects($this->once())->method('unserialize')->will($this->returnValue($requestData)); $closure = function () { return 'result'; }; + + $this->captchaHelperMock + ->expects($this->exactly(1)) + ->method('getCaptcha') + ->with('user_login') + ->will($this->returnValue($this->captchaMock)); + $this->assertEquals('result', $this->model->aroundExecute($this->loginControllerMock, $closure)); } + /** + * Test aroundExecuteIncorrectCaptcha. + */ public function testAroundExecuteIncorrectCaptcha() { $username = 'name'; @@ -128,18 +149,21 @@ public function testAroundExecuteIncorrectCaptcha() $this->captchaMock->expects($this->once())->method('logAttempt')->with($username); $this->captchaMock->expects($this->once())->method('isCorrect') ->with($captchaString)->will($this->returnValue(false)); - $this->serializerMock->expects(($this->once()))->method('unserialize')->will($this->returnValue($requestData)); + $this->serializerMock->expects($this->once())->method('unserialize')->will($this->returnValue($requestData)); $this->sessionManagerMock->expects($this->once())->method('setUsername')->with($username); $this->jsonFactoryMock->expects($this->once())->method('create') ->will($this->returnValue($this->resultJsonMock)); - $this->resultJsonMock->expects($this->once())->method('setData') - ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA')])->will($this->returnValue('response')); + $this->resultJsonMock + ->expects($this->once()) + ->method('setData') + ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA')]) + ->will($this->returnSelf()); $closure = function () { }; - $this->assertEquals('response', $this->model->aroundExecute($this->loginControllerMock, $closure)); + $this->assertEquals($this->resultJsonMock, $this->model->aroundExecute($this->loginControllerMock, $closure)); } /** @@ -151,7 +175,7 @@ public function testAroundExecuteCaptchaIsNotRequired($username, $requestContent { $this->requestMock->expects($this->once())->method('getContent') ->will($this->returnValue(json_encode($requestContent))); - $this->serializerMock->expects(($this->once()))->method('unserialize') + $this->serializerMock->expects($this->once())->method('unserialize') ->will($this->returnValue($requestContent)); $this->captchaMock->expects($this->once())->method('isRequired')->with($username) @@ -168,16 +192,39 @@ public function testAroundExecuteCaptchaIsNotRequired($username, $requestContent /** * @return array */ - public function aroundExecuteCaptchaIsNotRequired() + public function aroundExecuteCaptchaIsNotRequired(): array { return [ [ 'username' => 'name', 'requestData' => ['username' => 'name', 'captcha_string' => 'string'], ], + [ + 'username' => 'name', + 'requestData' => + [ + 'username' => 'name', + 'captcha_string' => 'string', + 'captcha_form_id' => $this->formIds[0] + ], + ], [ 'username' => null, - 'requestData' => ['captcha_string' => 'string'], + 'requestData' => + [ + 'username' => null, + 'captcha_string' => 'string', + 'captcha_form_id' => $this->formIds[0] + ], + ], + [ + 'username' => 'name', + 'requestData' => + [ + 'username' => 'name', + 'captcha_string' => 'string', + 'captcha_form_id' => null + ], ], ]; } diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php index 0500b29f787c2..1edbcc029e4ce 100644 --- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php @@ -24,7 +24,7 @@ class DefaultTest extends \PHPUnit\Framework\TestCase 'enable' => '1', 'font' => 'linlibertine', 'mode' => 'after_fail', - 'forms' => 'user_forgotpassword,user_create,guest_checkout,register_during_checkout', + 'forms' => 'user_forgotpassword,user_create', 'failed_attempts_login' => '3', 'failed_attempts_ip' => '1000', 'timeout' => '7', @@ -35,8 +35,6 @@ class DefaultTest extends \PHPUnit\Framework\TestCase 'always_for' => [ 'user_create', 'user_forgotpassword', - 'guest_checkout', - 'register_during_checkout', 'contact_us', ], ]; @@ -362,8 +360,7 @@ public function isShownToLoggedInUserDataProvider() return [ [true, 'contact_us'], [false, 'user_create'], - [false, 'user_forgotpassword'], - [false, 'guest_checkout'] + [false, 'user_forgotpassword'] ]; } } diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php deleted file mode 100644 index 89012ef653838..0000000000000 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckRegisterCheckoutObserverTest.php +++ /dev/null @@ -1,211 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Captcha\Test\Unit\Observer; - -use Magento\Captcha\Model\DefaultModel as CaptchaModel; -use Magento\Captcha\Observer\CheckRegisterCheckoutObserver; -use Magento\Captcha\Helper\Data as CaptchaDataHelper; -use Magento\Framework\App\Action\Action; -use Magento\Framework\App\ActionFlag; -use Magento\Captcha\Observer\CaptchaStringResolver; -use Magento\Checkout\Model\Type\Onepage; -use Magento\Framework\App\Request\Http; -use Magento\Framework\App\Response\Http as HttpResponse; -use Magento\Framework\Event\Observer; -use Magento\Framework\Json\Helper\Data as JsonHelper; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Quote\Model\Quote; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class CheckRegisterCheckoutObserverTest extends \PHPUnit\Framework\TestCase -{ - const FORM_ID = 'register_during_checkout'; - - /** - * @var CheckRegisterCheckoutObserver - */ - private $checkRegisterCheckoutObserver; - - /** - * @var ObjectManager - */ - private $objectManager; - - /** - * @var Observer - */ - private $observer; - - /** - * @var HttpResponse|\PHPUnit_Framework_MockObject_MockObject - */ - private $responseMock; - - /** - * @var HttpResponse|\PHPUnit_Framework_MockObject_MockObject - */ - private $requestMock; - - /** - * @var ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - private $actionFlagMock; - - /** - * @var CaptchaStringResolver|\PHPUnit_Framework_MockObject_MockObject - */ - private $captchaStringResolverMock; - - /** - * @var JsonHelper|\PHPUnit_Framework_MockObject_MockObject - */ - private $jsonHelperMock; - - /** - * @var CaptchaModel|\PHPUnit_Framework_MockObject_MockObject - */ - private $captchaModelMock; - - /** - * @var Quote|\PHPUnit_Framework_MockObject_MockObject - */ - private $quoteModelMock; - - /** - * @var Action|\PHPUnit_Framework_MockObject_MockObject - */ - private $controllerMock; - - protected function setUp() - { - $onepageModelTypeMock = $this->createMock(Onepage::class); - $captchaHelperMock = $this->createMock(CaptchaDataHelper::class); - $this->objectManager = new ObjectManager($this); - $this->actionFlagMock = $this->createMock(ActionFlag::class); - $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class); - $this->captchaModelMock = $this->createMock(CaptchaModel::class); - $this->quoteModelMock = $this->createMock(Quote::class); - $this->controllerMock = $this->createMock(Action::class); - $this->requestMock = $this->createMock(Http::class); - $this->responseMock = $this->createMock(HttpResponse::class); - $this->observer = new Observer(['controller_action' => $this->controllerMock]); - $this->jsonHelperMock = $this->createMock(JsonHelper::class); - - $this->checkRegisterCheckoutObserver = $this->objectManager->getObject( - CheckRegisterCheckoutObserver::class, - [ - 'helper' => $captchaHelperMock, - 'actionFlag' => $this->actionFlagMock, - 'captchaStringResolver' => $this->captchaStringResolverMock, - 'typeOnepage' => $onepageModelTypeMock, - 'jsonHelper' => $this->jsonHelperMock - ] - ); - - $captchaHelperMock->expects($this->once()) - ->method('getCaptcha') - ->with(self::FORM_ID) - ->willReturn($this->captchaModelMock); - $onepageModelTypeMock->expects($this->once()) - ->method('getQuote') - ->willReturn($this->quoteModelMock); - } - - public function testCheckRegisterCheckoutForGuest() - { - $this->quoteModelMock->expects($this->once()) - ->method('getCheckoutMethod') - ->willReturn(Onepage::METHOD_GUEST); - $this->captchaModelMock->expects($this->never()) - ->method('isRequired'); - - $this->checkRegisterCheckoutObserver->execute($this->observer); - } - - public function testCheckRegisterCheckoutWithNoCaptchaRequired() - { - $this->quoteModelMock->expects($this->once()) - ->method('getCheckoutMethod') - ->willReturn(Onepage::METHOD_REGISTER); - $this->captchaModelMock->expects($this->once()) - ->method('isRequired') - ->willReturn(false); - $this->captchaModelMock->expects($this->never()) - ->method('isCorrect'); - - $this->checkRegisterCheckoutObserver->execute($this->observer); - } - - public function testCheckRegisterCheckoutWithIncorrectCaptcha() - { - $captchaValue = 'some_word'; - $encodedJsonValue = '{}'; - - $this->quoteModelMock->expects($this->once()) - ->method('getCheckoutMethod') - ->willReturn(Onepage::METHOD_REGISTER); - $this->captchaModelMock->expects($this->once()) - ->method('isRequired') - ->willReturn(true); - $this->controllerMock->expects($this->once()) - ->method('getRequest') - ->willReturn($this->requestMock); - $this->controllerMock->expects($this->once()) - ->method('getResponse') - ->willReturn($this->responseMock); - $this->controllerMock->expects($this->once()) - ->method('getResponse') - ->willReturn($this->responseMock); - $this->captchaStringResolverMock->expects($this->once()) - ->method('resolve') - ->with($this->requestMock, self::FORM_ID) - ->willReturn($captchaValue); - $this->captchaModelMock->expects($this->once()) - ->method('isCorrect') - ->with($captchaValue) - ->willReturn(false); - $this->actionFlagMock->expects($this->once()) - ->method('set') - ->with('', Action::FLAG_NO_DISPATCH, true); - $this->jsonHelperMock->expects($this->once()) - ->method('jsonEncode') - ->willReturn($encodedJsonValue); - $this->responseMock->expects($this->once()) - ->method('representJson') - ->with($encodedJsonValue); - - $this->checkRegisterCheckoutObserver->execute($this->observer); - } - - public function testCheckRegisterCheckoutWithCorrectCaptcha() - { - $this->quoteModelMock->expects($this->once()) - ->method('getCheckoutMethod') - ->willReturn(Onepage::METHOD_REGISTER); - $this->captchaModelMock->expects($this->once()) - ->method('isRequired') - ->willReturn(true); - $this->controllerMock->expects($this->once()) - ->method('getRequest') - ->willReturn($this->requestMock); - $this->captchaStringResolverMock->expects($this->once()) - ->method('resolve') - ->with($this->requestMock, self::FORM_ID) - ->willReturn('some_word'); - $this->captchaModelMock->expects($this->once()) - ->method('isCorrect') - ->with('some_word') - ->willReturn(true); - $this->actionFlagMock->expects($this->never()) - ->method('set'); - - $this->checkRegisterCheckoutObserver->execute($this->observer); - } -} diff --git a/app/code/Magento/Captcha/etc/config.xml b/app/code/Magento/Captcha/etc/config.xml index 71c474de90ff4..dd748dd05ccda 100644 --- a/app/code/Magento/Captcha/etc/config.xml +++ b/app/code/Magento/Captcha/etc/config.xml @@ -53,8 +53,6 @@ <always_for> <user_create>1</user_create> <user_forgotpassword>1</user_forgotpassword> - <guest_checkout>1</guest_checkout> - <register_during_checkout>1</register_during_checkout> <contact_us>1</contact_us> </always_for> </captcha> @@ -77,12 +75,6 @@ <user_forgotpassword> <label>Forgot password</label> </user_forgotpassword> - <guest_checkout> - <label>Check Out as Guest</label> - </guest_checkout> - <register_during_checkout> - <label>Register during Checkout</label> - </register_during_checkout> <contact_us> <label>Contact Us</label> </contact_us> diff --git a/app/code/Magento/Captcha/etc/db_schema_whitelist.json b/app/code/Magento/Captcha/etc/db_schema_whitelist.json index 95fd6411b44dd..1f5b1b624e48b 100644 --- a/app/code/Magento/Captcha/etc/db_schema_whitelist.json +++ b/app/code/Magento/Captcha/etc/db_schema_whitelist.json @@ -1,13 +1,13 @@ { - "captcha_log": { - "column": { - "type": true, - "value": true, - "count": true, - "updated_at": true - }, - "constraint": { - "PRIMARY": true + "captcha_log": { + "column": { + "type": true, + "value": true, + "count": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Captcha/etc/di.xml b/app/code/Magento/Captcha/etc/di.xml index 955896eb12744..3a929f5e6cc00 100644 --- a/app/code/Magento/Captcha/etc/di.xml +++ b/app/code/Magento/Captcha/etc/di.xml @@ -33,7 +33,6 @@ <arguments> <argument name="formIds" xsi:type="array"> <item name="user_login" xsi:type="string">user_login</item> - <item name="guest_checkout" xsi:type="string">guest_checkout</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Captcha/etc/events.xml b/app/code/Magento/Captcha/etc/events.xml index e3ddd19de2d12..970c0d077260c 100644 --- a/app/code/Magento/Captcha/etc/events.xml +++ b/app/code/Magento/Captcha/etc/events.xml @@ -18,10 +18,6 @@ <event name="admin_user_authenticate_before"> <observer name="captcha" instance="Magento\Captcha\Observer\CheckUserLoginBackendObserver" /> </event> - <event name="controller_action_predispatch_checkout_onepage_saveBilling"> - <observer name="captcha_guest" instance="Magento\Captcha\Observer\CheckGuestCheckoutObserver" /> - <observer name="captcha_register" instance="Magento\Captcha\Observer\CheckRegisterCheckoutObserver" /> - </event> <event name="customer_customer_authenticated"> <observer name="captcha_reset_attempt" instance="Magento\Captcha\Observer\ResetAttemptForFrontendObserver" /> </event> diff --git a/app/code/Magento/Captcha/etc/frontend/di.xml b/app/code/Magento/Captcha/etc/frontend/di.xml index 225e62c8e8203..0c4ab0cda0735 100644 --- a/app/code/Magento/Captcha/etc/frontend/di.xml +++ b/app/code/Magento/Captcha/etc/frontend/di.xml @@ -17,7 +17,6 @@ <arguments> <argument name="formIds" xsi:type="array"> <item name="user_login" xsi:type="string">user_login</item> - <item name="guest_checkout" xsi:type="string">guest_checkout</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Captcha/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Captcha/view/frontend/layout/checkout_index_index.xml index 4ed56fd56cc3a..7180372f004e5 100644 --- a/app/code/Magento/Captcha/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Captcha/view/frontend/layout/checkout_index_index.xml @@ -36,29 +36,7 @@ <item name="captcha" xsi:type="array"> <item name="component" xsi:type="string">Magento_Captcha/js/view/checkout/loginCaptcha</item> <item name="displayArea" xsi:type="string">additional-login-form-fields</item> - <item name="formId" xsi:type="string">guest_checkout</item> - <item name="configSource" xsi:type="string">checkoutConfig</item> - </item> - </item> - </item> - </item> - </item> - </item> - </item> - </item> - </item> - <item name="billing-step" xsi:type="array"> - <item name="children" xsi:type="array"> - <item name="payment" xsi:type="array"> - <item name="children" xsi:type="array"> - <item name="customer-email" xsi:type="array"> - <item name="children" xsi:type="array"> - <item name="additional-login-form-fields" xsi:type="array"> - <item name="children" xsi:type="array"> - <item name="captcha" xsi:type="array"> - <item name="component" xsi:type="string">Magento_Captcha/js/view/checkout/loginCaptcha</item> - <item name="displayArea" xsi:type="string">additional-login-form-fields</item> - <item name="formId" xsi:type="string">guest_checkout</item> + <item name="formId" xsi:type="string">user_login</item> <item name="configSource" xsi:type="string">checkoutConfig</item> </item> </item> diff --git a/app/code/Magento/Captcha/view/frontend/templates/default.phtml b/app/code/Magento/Captcha/view/frontend/templates/default.phtml index 980a78ff0f7c5..6c9a5fe85f596 100644 --- a/app/code/Magento/Captcha/view/frontend/templates/default.phtml +++ b/app/code/Magento/Captcha/view/frontend/templates/default.phtml @@ -14,7 +14,7 @@ $captcha = $block->getCaptchaModel(); <div class="field captcha required" role="<?= $block->escapeHtmlAttr($block->getFormId()) ?>"> <label for="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" class="label"><span><?= $block->escapeHtml(__('Please type the letters and numbers below')) ?></span></label> <div class="control captcha"> - <input name="<?= $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE) ?>[<?= $block->escapeHtmlAttr($block->getFormId()) ?>]" type="text" class="input-text required-entry" data-validate="{required:true}" id="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" /> + <input name="<?= $block->escapeHtmlAttr(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE) ?>[<?= $block->escapeHtmlAttr($block->getFormId()) ?>]" type="text" class="input-text required-entry" data-validate="{required:true}" id="captcha_<?= $block->escapeHtmlAttr($block->getFormId()) ?>" autocomplete="off"/> <div class="nested"> <div class="field captcha no-label" data-captcha="<?= $block->escapeHtmlAttr($block->getFormId()) ?>" diff --git a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html index 1e2f595a0087f..575b3ca6f732e 100644 --- a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html +++ b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html @@ -8,13 +8,14 @@ <div class="field captcha required" data-bind="blockLoader: getIsLoading()"> <label data-bind="attr: {for: 'captcha_' + formId}" class="label"><span data-bind="i18n: 'Please type the letters and numbers below'"></span></label> <div class="control captcha"> - <input name="captcha_string" type="text" class="input-text required-entry" data-bind="value: captchaValue(), attr: {id: 'captcha_' + formId, 'data-scope': dataScope}" /> + <input name="captcha_string" type="text" class="input-text required-entry" data-bind="value: captchaValue(), attr: {id: 'captcha_' + formId, 'data-scope': dataScope}" autocomplete="off"/> <input name="captcha_form_id" type="hidden" data-bind="value: formId, attr: {'data-scope': dataScope}" /> <div class="nested"> <div class="field captcha no-label"> <div class="control captcha-image"> <img data-bind="attr: { alt: $t('Please type the letters and numbers below'), + title: $t('Please type the letters and numbers below'), height: imageHeight(), src: getImageSource(), }" diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Checkboxes/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Checkboxes/Tree.php index ad1ccea6f2c5a..1e0015d2be2c6 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Checkboxes/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Checkboxes/Tree.php @@ -30,7 +30,7 @@ class Tree extends \Magento\Catalog\Block\Adminhtml\Category\Tree */ protected function _prepareLayout() { - $this->setTemplate('catalog/category/checkboxes/tree.phtml'); + $this->setTemplate('Magento_Catalog::catalog/category/checkboxes/tree.phtml'); } /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php index 0026e52e039ef..cd6c5021f0cc9 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php @@ -32,10 +32,9 @@ protected function _getElementHtml(AbstractElement $element) $from = $element->setValue(isset($values[0]) ? $values[0] : null)->getElementHtml(); $to = $element->setValue(isset($values[1]) ? $values[1] : null)->getElementHtml(); - return __( - '<label class="label"><span>from</span></label>' - ) . $from . __( - '<label class="label"><span>to</span></label>' - ) . $to; + return '<label class="label"><span>' . __('from') . '</span></label>' + . $from . + '<label class="label"><span>' . __('to') . '</span></label>' + . $to; } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php index f5e3f94418687..cb0a739b56e4e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php @@ -14,5 +14,5 @@ class Attribute extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'catalog/product/attribute/set/main/tree/attribute.phtml'; + protected $_template = 'Magento_Catalog::catalog/product/attribute/set/main/tree/attribute.phtml'; } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php index a7129f509316b..3d131a6e08810 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php @@ -41,7 +41,7 @@ public function __construct( public function _construct() { parent::_construct(); - $this->setTemplate('catalog/product/edit/serializer.phtml'); + $this->setTemplate('Magento_Catalog::catalog/product/edit/serializer.phtml'); return $this; } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php index f30b37877b78f..6cb6f0e526e9f 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php @@ -102,7 +102,7 @@ public function getElementHtml() */ public function getImages() { - return $this->registry->registry('current_product')->getData('media_gallery') ?: null; + return $this->getDataObject()->getData('media_gallery') ?: null; } /** @@ -117,7 +117,7 @@ public function getContentHtml() $content->setId($this->getHtmlId() . '_content')->setElement($this); $content->setFormName($this->formName); $galleryJs = $content->getJsObjectName(); - $content->getUploader()->getConfig()->setMegiaGallery($galleryJs); + $content->getUploader()->getConfig()->setMediaGallery($galleryJs); return $content->toHtml(); } diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php index b353e477a056c..cb59d86a74512 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php +++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php @@ -67,12 +67,11 @@ public function getProduct() } /** - * $excludeAttr is optional array of attribute codes to - * exclude them from additional data array + * $excludeAttr is optional array of attribute codes to exclude them from additional data array * * @param array $excludeAttr * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAdditionalData(array $excludeAttr = []) { @@ -80,7 +79,7 @@ public function getAdditionalData(array $excludeAttr = []) $product = $this->getProduct(); $attributes = $product->getAttributes(); foreach ($attributes as $attribute) { - if ($attribute->getIsVisibleOnFront() && !in_array($attribute->getAttributeCode(), $excludeAttr)) { + if ($this->isVisibleOnFrontend($attribute, $excludeAttr)) { $value = $attribute->getFrontend()->getValue($product); if ($value instanceof Phrase) { @@ -100,4 +99,18 @@ public function getAdditionalData(array $excludeAttr = []) } return $data; } + + /** + * Determine if we should display the attribute on the front-end + * + * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute + * @param array $excludeAttr + * @return bool + */ + protected function isVisibleOnFrontend( + \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute, + array $excludeAttr + ) { + return ($attribute->getIsVisibleOnFront() && !in_array($attribute->getAttributeCode(), $excludeAttr)); + } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php index 8f570e35989cb..0a54475b15f9c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php @@ -44,12 +44,12 @@ public function execute() $this->_eventManager->dispatch('catalog_controller_category_delete', ['category' => $category]); $this->_auth->getAuthStorage()->setDeletedPath($category->getPath()); $this->categoryRepository->delete($category); - $this->messageManager->addSuccess(__('You deleted the category.')); + $this->messageManager->addSuccessMessage(__('You deleted the category.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return $resultRedirect->setPath('catalog/*/edit', ['_current' => true]); } catch (\Exception $e) { - $this->messageManager->addError(__('Something went wrong while trying to delete the category.')); + $this->messageManager->addErrorMessage(__('Something went wrong while trying to delete the category.')); return $resultRedirect->setPath('catalog/*/edit', ['_current' => true]); } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php index cc03ab870739b..aa9ae88754b6a 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php @@ -280,7 +280,7 @@ public function imagePreprocessing($data) continue; } - $data[$attributeCode] = false; + $data[$attributeCode] = ''; } return $data; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php index 775097c5eba1d..ca7652ebb43b5 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php @@ -54,7 +54,7 @@ protected function _validateProducts() } if ($error) { - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); } return !$error; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php index 82496446aef9f..0fbf9054ef1bd 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php @@ -192,7 +192,7 @@ public function execute() $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]); } - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('A total of %1 record(s) were updated.', count($this->attributeHelper->getProductIds())) ); @@ -205,9 +205,9 @@ public function execute() $this->_productPriceIndexerProcessor->reindexList($this->attributeHelper->getProductIds()); } } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while updating the product(s) attributes.') ); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php index bb18436be6102..a873f08d082d7 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php @@ -68,7 +68,7 @@ public function execute() $response->setError(true); $response->setMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while updating the product(s) attributes.') ); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php index cc5a658a9296d..bef6aee0e2afd 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php @@ -21,23 +21,23 @@ public function execute() // entity type check $model->load($id); if ($model->getEntityTypeId() != $this->_entityTypeId) { - $this->messageManager->addError(__('We can\'t delete the attribute.')); + $this->messageManager->addErrorMessage(__('We can\'t delete the attribute.')); return $resultRedirect->setPath('catalog/*/'); } try { $model->delete(); - $this->messageManager->addSuccess(__('You deleted the product attribute.')); + $this->messageManager->addSuccessMessage(__('You deleted the product attribute.')); return $resultRedirect->setPath('catalog/*/'); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return $resultRedirect->setPath( 'catalog/*/edit', ['attribute_id' => $this->getRequest()->getParam('attribute_id')] ); } } - $this->messageManager->addError(__('We can\'t find an attribute to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find an attribute to delete.')); return $resultRedirect->setPath('catalog/*/'); } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php index fd97aaa50389e..a99cbdbade181 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php @@ -25,14 +25,14 @@ public function execute() $model->load($id); if (!$model->getId()) { - $this->messageManager->addError(__('This attribute no longer exists.')); + $this->messageManager->addErrorMessage(__('This attribute no longer exists.')); $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('catalog/*/'); } // entity type check if ($model->getEntityTypeId() != $this->_entityTypeId) { - $this->messageManager->addError(__('This attribute cannot be edited.')); + $this->messageManager->addErrorMessage(__('This attribute cannot be edited.')); $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('catalog/*/'); } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index b61be2b95b960..db452113ada06 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -92,9 +92,7 @@ public function execute() $attributeSet->setEntityTypeId($this->_entityTypeId)->load($setName, 'attribute_set_name'); if ($attributeSet->getId()) { $setName = $this->_objectManager->get(\Magento\Framework\Escaper::class)->escapeHtml($setName); - $this->messageManager->addError( - __('A "%1" attribute set name already exists. Create a new name and try again.', $setName) - ); + $this->messageManager->addErrorMessage(__('An attribute set named \'%1\' already exists.', $setName)); $layout = $this->layoutFactory->create(); $layout->initMessages(); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php index 7e8b03a66f603..63e52eead064c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php @@ -43,11 +43,11 @@ public function execute() $product = $this->productBuilder->build($this->getRequest()); try { $newProduct = $this->productCopier->copy($product); - $this->messageManager->addSuccess(__('You duplicated the product.')); + $this->messageManager->addSuccessMessage(__('You duplicated the product.')); $resultRedirect->setPath('catalog/*/edit', ['_current' => true, 'id' => $newProduct->getId()]); } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $resultRedirect->setPath('catalog/*/edit', ['_current' => true]); } return $resultRedirect; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php index 838bce7272250..1b9316a95ad59 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php @@ -52,12 +52,12 @@ public function execute() if (($productId && !$product->getEntityId())) { /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); - $this->messageManager->addError(__('This product doesn\'t exist.')); + $this->messageManager->addErrorMessage(__('This product doesn\'t exist.')); return $resultRedirect->setPath('catalog/*/'); } elseif ($productId === 0) { /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); - $this->messageManager->addError(__('Invalid product id. Should be numeric value greater than 0')); + $this->messageManager->addErrorMessage(__('Invalid product id. Should be numeric value greater than 0')); return $resultRedirect->setPath('catalog/*/'); } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php index 4909e22775e55..8a5f375f2b706 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php @@ -29,12 +29,12 @@ public function execute() ); if ($model->itemExists()) { - $this->messageManager->addError(__('A group with the same name already exists.')); + $this->messageManager->addErrorMessage(__('A group with the same name already exists.')); } else { try { $model->save(); } catch (\Exception $e) { - $this->messageManager->addError(__('Something went wrong while saving this group.')); + $this->messageManager->addErrorMessage(__('Something went wrong while saving this group.')); } } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index 95339870b4d61..d82f4a04fb252 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -104,7 +104,7 @@ class Helper * @param \Magento\Backend\Helper\Js $jsHelper * @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter * @param CustomOptionFactory|null $customOptionFactory - * @param ProductLinkFactory |null $productLinkFactory + * @param ProductLinkFactory|null $productLinkFactory * @param ProductRepositoryInterface|null $productRepository * @param LinkTypeProvider|null $linkTypeProvider * @param AttributeFilter|null $attributeFilter @@ -159,6 +159,7 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra } $productData = $this->normalize($productData); + $productData = $this->convertSpecialFromDateStringToObject($productData); if (!empty($productData['is_downloadable'])) { $productData['product_has_weight'] = 0; @@ -452,4 +453,19 @@ private function fillProductOptions(Product $product, array $productOptions) return $product->setOptions($customOptions); } + + /** + * Convert string date presentation into object + * + * @param array $productData + * @return array + */ + private function convertSpecialFromDateStringToObject($productData) + { + if (isset($productData['special_from_date']) && $productData['special_from_date'] != '') { + $productData['special_from_date'] = new \DateTime($productData['special_from_date']); + } + + return $productData; + } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php index 2402fb213cda0..f32c6edd57394 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php @@ -64,9 +64,12 @@ public function execute() $this->productRepository->delete($product); $productDeleted++; } - $this->messageManager->addSuccess( - __('A total of %1 record(s) have been deleted.', $productDeleted) - ); + + if ($productDeleted) { + $this->messageManager->addSuccessMessage( + __('A total of %1 record(s) have been deleted.', $productDeleted) + ); + } return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('catalog/*/index'); } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php index 84837262a8154..ff3ce60d92787 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php @@ -204,7 +204,8 @@ private function handleImageRemoveError($postData, $productId) if ($removedImagesAmount) { $expectedImagesAmount = count($postData['product']['media_gallery']['images']) - $removedImagesAmount; $product = $this->productRepository->getById($productId); - if ($expectedImagesAmount != count($product->getMediaGallery('images'))) { + $images = $product->getMediaGallery('images'); + if (is_array($images) && $expectedImagesAmount != count($images)) { $this->messageManager->addNoticeMessage( __('The image cannot be removed as it has been assigned to the other image role') ); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php index b49a4dabe223c..f2695311732f0 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php @@ -36,10 +36,10 @@ public function execute() $resultRedirect = $this->resultRedirectFactory->create(); try { $this->attributeSetRepository->deleteById($setId); - $this->messageManager->addSuccess(__('The attribute set has been removed.')); + $this->messageManager->addSuccessMessage(__('The attribute set has been removed.')); $resultRedirect->setPath('catalog/*/'); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t delete this set right now.')); + $this->messageManager->addErrorMessage(__('We can\'t delete this set right now.')); $resultRedirect->setUrl($this->_redirect->getRedirectUrl($this->getUrl('*'))); } return $resultRedirect; diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php index dfddcf7e92b97..c5dd9ce6d8e77 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php @@ -127,15 +127,15 @@ public function execute() $model->initFromSkeleton($this->getRequest()->getParam('skeleton_set')); } $model->save(); - $this->messageManager->addSuccess(__('You saved the attribute set.')); + $this->messageManager->addSuccessMessage(__('You saved the attribute set.')); } catch (\Magento\Framework\Exception\AlreadyExistsException $e) { $this->messageManager->addErrorMessage($e->getMessage()); $hasError = true; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $hasError = true; } catch (\Exception $e) { - $this->messageManager->addException($e, __('Something went wrong while saving the attribute set.')); + $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the attribute set.')); $hasError = true; } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php index 63f46fd32e6f7..e131bfe38c546 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php @@ -137,7 +137,7 @@ public function execute() $response->setError(true); $response->setMessages([$e->getMessage()]); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $layout = $this->layoutFactory->create(); $layout->initMessages(); $response->setError(true); diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php index a72f764a5f1f6..226e572505076 100644 --- a/app/code/Magento/Catalog/Controller/Category/View.php +++ b/app/code/Magento/Catalog/Controller/Category/View.php @@ -111,7 +111,7 @@ public function __construct( /** * Initialize requested category object * - * @return \Magento\Catalog\Model\Category + * @return \Magento\Catalog\Model\Category|bool */ protected function _initCategory() { diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php index 89eb6c9be929f..eb9cc83125541 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php @@ -36,7 +36,14 @@ public function execute() $productName = $this->_objectManager->get( \Magento\Framework\Escaper::class )->escapeHtml($product->getName()); - $this->messageManager->addSuccess(__('You added product %1 to the comparison list.', $productName)); + $this->messageManager->addComplexSuccessMessage( + 'addCompareSuccessMessage', + [ + 'product_name' => $productName, + 'compare_list_url' => $this->_url->getUrl('catalog/product_compare') + ] + ); + $this->_eventManager->dispatch('catalog_product_compare_add_product', ['product' => $product]); } diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php index 30470d13f002d..568fbf1d05677 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php @@ -30,12 +30,12 @@ public function execute() try { $items->clear(); - $this->messageManager->addSuccess(__('You cleared the comparison list.')); + $this->messageManager->addSuccessMessage(__('You cleared the comparison list.')); $this->_objectManager->get(\Magento\Catalog\Helper\Product\Compare::class)->calculate(); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Something went wrong clearing the comparison list.')); + $this->messageManager->addExceptionMessage($e, __('Something went wrong clearing the comparison list.')); } /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php index fadb94761a236..2acbe5ce4d582 100644 --- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php +++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php @@ -44,7 +44,7 @@ public function execute() $item->delete(); $productName = $this->_objectManager->get(\Magento\Framework\Escaper::class) ->escapeHtml($product->getName()); - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('You removed product %1 from the comparison list.', $productName) ); $this->_eventManager->dispatch( diff --git a/app/code/Magento/Catalog/Controller/Product/View.php b/app/code/Magento/Catalog/Controller/Product/View.php index 4c577eb897589..ed437361fddd3 100644 --- a/app/code/Magento/Catalog/Controller/Product/View.php +++ b/app/code/Magento/Catalog/Controller/Product/View.php @@ -78,13 +78,16 @@ public function execute() if ($this->getRequest()->isPost() && $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) { $product = $this->_initProduct(); + if (!$product) { return $this->noProductRedirect(); } + if ($specifyOptions) { $notice = $product->getTypeInstance()->getSpecifyOptionMessage(); - $this->messageManager->addNotice($notice); + $this->messageManager->addNoticeMessage($notice); } + if ($this->getRequest()->isAjax()) { $this->getResponse()->representJson( $this->_objectManager->get(\Magento\Framework\Json\Helper\Data::class)->jsonEncode([ diff --git a/app/code/Magento/Catalog/CustomerData/CompareProducts.php b/app/code/Magento/Catalog/CustomerData/CompareProducts.php index 0e688042c615a..afbeab8c9070e 100644 --- a/app/code/Magento/Catalog/CustomerData/CompareProducts.php +++ b/app/code/Magento/Catalog/CustomerData/CompareProducts.php @@ -19,6 +19,11 @@ class CompareProducts implements SectionSourceInterface */ protected $productUrl; + /** + * @var \Magento\Catalog\Helper\Output + */ + private $outputHelper; + /** * @param \Magento\Catalog\Helper\Product\Compare $helper * @param \Magento\Catalog\Model\Product\Url $productUrl @@ -54,6 +59,7 @@ public function getSectionData() protected function getItems() { $items = []; + /** @var \Magento\Catalog\Model\Product $item */ foreach ($this->helper->getItemCollection() as $item) { $items[] = [ 'id' => $item->getId(), diff --git a/app/code/Magento/Catalog/Helper/Data.php b/app/code/Magento/Catalog/Helper/Data.php index 9eebcdb2df34f..ae20cda460796 100644 --- a/app/code/Magento/Catalog/Helper/Data.php +++ b/app/code/Magento/Catalog/Helper/Data.php @@ -32,6 +32,10 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper const CONFIG_USE_STATIC_URLS = 'cms/wysiwyg/use_static_urls_in_catalog'; + /** + * @deprecated + * @see \Magento\Catalog\Helper\Output::isDirectivesExists + */ const CONFIG_PARSE_URL_DIRECTIVES = 'catalog/frontend/parse_url_directives'; const XML_PATH_DISPLAY_PRODUCT_COUNT = 'catalog/layered_navigation/display_product_count'; @@ -443,6 +447,8 @@ public function isUsingStaticUrlsAllowed() * Check if the parsing of URL directives is allowed for the catalog * * @return bool + * @deprecated + * @see \Magento\Catalog\Helper\Output::isDirectivesExists */ public function isUrlDirectivesParsingAllowed() { @@ -457,6 +463,7 @@ public function isUrlDirectivesParsingAllowed() * Retrieve template processor for catalog content * * @return \Magento\Framework\Filter\Template + * @throws \Magento\Framework\Exception\LocalizedException */ public function getPageTemplateProcessor() { diff --git a/app/code/Magento/Catalog/Helper/Output.php b/app/code/Magento/Catalog/Helper/Output.php index facd5351f269a..ad0f9508cb67e 100644 --- a/app/code/Magento/Catalog/Helper/Output.php +++ b/app/code/Magento/Catalog/Helper/Output.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Helper; use Magento\Catalog\Model\Category as ModelCategory; @@ -45,20 +47,29 @@ class Output extends \Magento\Framework\App\Helper\AbstractHelper protected $_escaper; /** + * @var array + */ + private $directivePatterns; + + /** + * Output constructor. * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Eav\Model\Config $eavConfig * @param Data $catalogData * @param \Magento\Framework\Escaper $escaper + * @param array $directivePatterns */ public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Eav\Model\Config $eavConfig, Data $catalogData, - \Magento\Framework\Escaper $escaper + \Magento\Framework\Escaper $escaper, + $directivePatterns = [] ) { $this->_eavConfig = $eavConfig; $this->_catalogData = $catalogData; $this->_escaper = $escaper; + $this->directivePatterns = $directivePatterns; parent::__construct($context); } @@ -134,6 +145,7 @@ public function process($method, $result, $params) * @param string $attributeName * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws \Magento\Framework\Exception\LocalizedException */ public function productAttribute($product, $attributeHtml, $attributeName) { @@ -151,10 +163,12 @@ public function productAttribute($product, $attributeHtml, $attributeName) $attributeHtml = nl2br($attributeHtml); } } - if ($attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled()) { - if ($this->_catalogData->isUrlDirectivesParsingAllowed()) { - $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); - } + if ($attributeHtml !== null + && $attribute->getIsHtmlAllowedOnFront() + && $attribute->getIsWysiwygEnabled() + && $this->isDirectivesExists($attributeHtml) + ) { + $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); } $attributeHtml = $this->process( @@ -173,6 +187,7 @@ public function productAttribute($product, $attributeHtml, $attributeName) * @param string $attributeHtml * @param string $attributeName * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function categoryAttribute($category, $attributeHtml, $attributeName) { @@ -185,10 +200,13 @@ public function categoryAttribute($category, $attributeHtml, $attributeName) ) { $attributeHtml = $this->_escaper->escapeHtml($attributeHtml); } - if ($attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled()) { - if ($this->_catalogData->isUrlDirectivesParsingAllowed()) { - $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); - } + if ($attributeHtml !== null + && $attribute->getIsHtmlAllowedOnFront() + && $attribute->getIsWysiwygEnabled() + && $this->isDirectivesExists($attributeHtml) + + ) { + $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml); } $attributeHtml = $this->process( 'categoryAttribute', @@ -197,4 +215,22 @@ public function categoryAttribute($category, $attributeHtml, $attributeName) ); return $attributeHtml; } + + /** + * Check if string has directives + * + * @param string $attributeHtml + * @return bool + */ + public function isDirectivesExists($attributeHtml) + { + $matches = false; + foreach ($this->directivePatterns as $pattern) { + if (preg_match($pattern, $attributeHtml)) { + $matches = true; + break; + } + } + return $matches; + } } diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php index f8cf810ffb570..8de708dd467c8 100644 --- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php +++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php @@ -21,8 +21,13 @@ class ProductCategoryFilter implements CustomFilterInterface */ public function apply(Filter $filter, AbstractDb $collection) { - $conditionType = $filter->getConditionType() ?: 'eq'; - $categoryFilter = [$conditionType => [$filter->getValue()]]; + $value = $filter->getValue(); + $conditionType = $filter->getConditionType() ?: 'in'; + $filterValue = [$value]; + if (($conditionType === 'in' || $conditionType === 'nin') && is_string($value)) { + $filterValue = explode(',', $value); + } + $categoryFilter = [$conditionType => $filterValue]; /** @var Collection $collection */ $collection->addCategoriesFilter($categoryFilter); diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index a2dff83173b37..cd450e26cd832 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -106,7 +106,7 @@ public function beforeSave($object) $object->setData($this->additionalData . $attributeName, $value); $object->setData($attributeName, $imageName); } elseif (!is_string($value)) { - $object->setData($attributeName, ''); + $object->setData($attributeName, null); } return parent::beforeSave($object); diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php index d2c11a3762961..1c20608670672 100644 --- a/app/code/Magento/Catalog/Model/ImageExtractor.php +++ b/app/code/Magento/Catalog/Model/ImageExtractor.php @@ -32,12 +32,16 @@ public function process(\DOMElement $mediaNode, $mediaParentTag) continue; } $attributeTagName = $attribute->tagName; - if ($attributeTagName === 'background') { - $nodeValue = $this->processImageBackground($attribute->nodeValue); - } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { - $nodeValue = intval($attribute->nodeValue); + if ((bool)$attribute->getAttribute('xsi:nil') !== true) { + if ($attributeTagName === 'background') { + $nodeValue = $this->processImageBackground($attribute->nodeValue); + } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { + $nodeValue = intval($attribute->nodeValue); + } else { + $nodeValue = $attribute->nodeValue; + } } else { - $nodeValue = !in_array($attribute->nodeValue, ['false', '0']); + $nodeValue = null; } $result[$mediaParentTag][$moduleNameImage][Image::MEDIA_TYPE_CONFIG_NODE][$imageId][$attribute->tagName] = $nodeValue; diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php index 09dbed350c5e4..f8121b55dbf99 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -8,6 +8,7 @@ use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; use Magento\Framework\DB\Query\Generator as QueryGenerator; use Magento\Framework\App\ResourceConnection; +use Magento\Indexer\Model\ProcessManager; /** * Class Full reindex action @@ -44,6 +45,11 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio */ private $activeTableSwitcher; + /** + * @var ProcessManager + */ + private $processManager; + /** * @param ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -52,9 +58,10 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio * @param \Magento\Framework\Indexer\BatchSizeManagementInterface|null $batchSizeManagement * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool - * @param \Magento\Indexer\Model\Indexer\StateFactory|null $stateFactory * @param int|null $batchRowsCount * @param ActiveTableSwitcher|null $activeTableSwitcher + * @param ProcessManager $processManager + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, @@ -65,7 +72,8 @@ public function __construct( \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, $batchRowsCount = null, - ActiveTableSwitcher $activeTableSwitcher = null + ActiveTableSwitcher $activeTableSwitcher = null, + ProcessManager $processManager = null ) { parent::__construct( $resource, @@ -85,6 +93,7 @@ public function __construct( ); $this->batchRowsCount = $batchRowsCount; $this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class); + $this->processManager = $processManager ?: $objectManager->get(ProcessManager::class); } /** @@ -133,6 +142,38 @@ public function execute() return $this; } + /** + * Run reindexation + * + * @return void + */ + protected function reindex() + { + $userFunctions = []; + + foreach ($this->storeManager->getStores() as $store) { + if ($this->getPathFromCategoryId($store->getRootCategoryId())) { + $userFunctions[$store->getId()] = function () use ($store) { + return $this->reindexStore($store); + }; + } + } + + $this->processManager->execute($userFunctions); + } + + /** + * Execute indexation by store + * + * @param \Magento\Store\Model\Store $store + */ + private function reindexStore($store) + { + $this->reindexRootCategory($store); + $this->reindexAnchorCategories($store); + $this->reindexNonAnchorCategories($store); + } + /** * Publish data from tmp to replica table * diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php index 9f4e19bf95a8d..005936a75e6d6 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php @@ -97,7 +97,7 @@ protected function validate(AbstractModel $group) public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $storeGroup) { foreach ($storeGroup->getStores() as $store) { - $this->tableMaintainer->dropTablesForStore($store->getId()); + $this->tableMaintainer->dropTablesForStore((int)$store->getId()); } return $objectResource; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php index 114d2a94f5b35..b6f9e6adf4a1c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php @@ -51,7 +51,7 @@ public function afterSave(AbstractDb $subject, AbstractDb $objectResource, Abstr */ public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store) { - $this->tableMaintainer->dropTablesForStore($store->getId()); + $this->tableMaintainer->dropTablesForStore((int)$store->getId()); return $objectResource; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php index 387a8085310e4..50700e672237e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php @@ -39,7 +39,7 @@ public function __construct( public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website) { foreach ($website->getStoreIds() as $storeId) { - $this->tableMaintainer->dropTablesForStore($storeId); + $this->tableMaintainer->dropTablesForStore((int)$storeId); } return $objectResource; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php index 1278434fcad43..3c2629bc570f2 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php @@ -91,6 +91,8 @@ private function getTable($table) * @param string $newTableName * * @return void + * + * @throws \Zend_Db_Exception */ private function createTable($mainTableName, $newTableName) { @@ -135,11 +137,13 @@ public function getMainTable(int $storeId) * @param $storeId * * @return void + * + * @throws \Zend_Db_Exception */ public function createTablesForStore(int $storeId) { $mainTableName = $this->getMainTable($storeId); - //Create index table for store based on on main replica table + //Create index table for store based on main replica table //Using main replica table is necessary for backward capability and TableResolver plugin work $this->createTable( $this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix), @@ -206,12 +210,12 @@ public function createMainTmpTable(int $storeId) * * @return string * - * @throws \Exception + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getMainTmpTable(int $storeId) { if (!isset($this->mainTmpTable[$storeId])) { - throw new \Exception('Temporary table does not exist'); + throw new \Magento\Framework\Exception\NoSuchEntityException('Temporary table does not exist'); } return $this->mainTmpTable[$storeId]; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index 182f04de4ab0e..a218266c25034 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -161,7 +161,7 @@ protected function removeEntries() $this->getIndexTable($store->getId()), ['product_id IN (?)' => $this->limitationByProducts] ); - }; + } } /** @@ -228,7 +228,7 @@ private function getCategoryIdsFromIndex(array $productIds) ->distinct() ) ); - }; + } $parentCategories = $categoryIds; foreach ($categoryIds as $categoryId) { $parentIds = explode('/', $this->getPathFromCategoryId($categoryId)); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php index b6206f96b91e0..6101e5cd362e4 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Product\Eav; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\AbstractEav; @@ -12,6 +14,11 @@ */ abstract class AbstractAction { + /** + * Config path for enable EAV indexer + */ + const ENABLE_EAV_INDEXER = 'catalog/search/enable_eav_indexer'; + /** * EAV Indexers by type * @@ -29,17 +36,27 @@ abstract class AbstractAction */ protected $_eavDecimalFactory; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + /** * AbstractAction constructor. * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory + * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory + \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null ) { $this->_eavDecimalFactory = $eavDecimalFactory; $this->_eavSourceFactory = $eavSourceFactory; + $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Config\ScopeConfigInterface::class + ); } /** @@ -92,6 +109,9 @@ public function getIndexer($type) */ public function reindex($ids = null) { + if (!$this->isEavIndexerEnabled()) { + return; + } foreach ($this->getIndexers() as $indexer) { if ($ids === null) { $indexer->reindexAll(); @@ -149,4 +169,19 @@ protected function processRelations(AbstractEav $indexer, array $ids, bool $only return array_unique(array_merge($ids, $childIds, $parentIds)); } + + /** + * Get EAV indexer status + * + * @return bool + */ + private function isEavIndexerEnabled(): bool + { + $eavIndexerStatus = $this->scopeConfig->getValue( + self::ENABLE_EAV_INDEXER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + + return (bool)$eavIndexerStatus; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php index bc747e62f641e..802176092d147 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Product\Eav\Action; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; /** * Class Full reindex action + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction { @@ -32,6 +35,11 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction */ private $activeTableSwitcher; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory @@ -39,6 +47,7 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator * @param ActiveTableSwitcher|null $activeTableSwitcher + * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory, @@ -46,9 +55,13 @@ public function __construct( \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator = null, - ActiveTableSwitcher $activeTableSwitcher = null + ActiveTableSwitcher $activeTableSwitcher = null, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null ) { - parent::__construct($eavDecimalFactory, $eavSourceFactory); + $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Config\ScopeConfigInterface::class + ); + parent::__construct($eavDecimalFactory, $eavSourceFactory, $scopeConfig); $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance()->get( \Magento\Framework\EntityManager\MetadataPool::class ); @@ -73,6 +86,9 @@ public function __construct( */ public function execute($ids = null) { + if (!$this->isEavIndexerEnabled()) { + return; + } try { foreach ($this->getIndexers() as $indexerName => $indexer) { $connection = $indexer->getConnection(); @@ -129,4 +145,19 @@ protected function syncData($indexer, $destinationTable, $ids = null) throw $e; } } + + /** + * Get EAV indexer status + * + * @return bool + */ + private function isEavIndexerEnabled(): bool + { + $eavIndexerStatus = $this->scopeConfig->getValue( + self::ENABLE_EAV_INDEXER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + + return (bool)$eavIndexerStatus; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php index 9dd312e9da801..3a1611299288c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php @@ -175,6 +175,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '') if (!empty($updateData)) { $updateData += ['entity_id' => $productId]; + $updateData += ['row_id' => $productId]; $updateFields = []; foreach ($updateData as $key => $value) { $updateFields[$key] = $key; diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index 7aed842713f5d..e9a907f0b5097 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -5,7 +5,11 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Price; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; /** * Abstract action reindex class @@ -17,7 +21,7 @@ abstract class AbstractAction /** * Default Product Type Price indexer resource model * - * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice + * @var DefaultPrice */ protected $_defaultIndexerResource; @@ -77,6 +81,16 @@ abstract class AbstractAction */ private $tierPriceIndexResource; + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory + */ + private $dimensionCollectionFactory; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -85,8 +99,13 @@ abstract class AbstractAction * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Catalog\Model\Product\Type $catalogProductType * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource + * @param DefaultPrice $defaultIndexerResource + * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice|null $tierPriceIndexResource + * @param DimensionCollectionFactory|null $dimensionCollectionFactory + * @param TableMaintainer|null $tableMaintainer + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $config, @@ -96,8 +115,10 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Catalog\Model\Product\Type $catalogProductType, \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null + DefaultPrice $defaultIndexerResource, + \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null, + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null, + \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $tableMaintainer = null ) { $this->_config = $config; $this->_storeManager = $storeManager; @@ -108,9 +129,15 @@ public function __construct( $this->_indexerPriceFactory = $indexerPriceFactory; $this->_defaultIndexerResource = $defaultIndexerResource; $this->_connection = $this->_defaultIndexerResource->getConnection(); - $this->tierPriceIndexResource = $tierPriceIndexResource ?: ObjectManager::getInstance()->get( + $this->tierPriceIndexResource = $tierPriceIndexResource ?? ObjectManager::getInstance()->get( \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice::class ); + $this->dimensionCollectionFactory = $dimensionCollectionFactory ?? ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + ); + $this->tableMaintainer = $tableMaintainer ?? ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class + ); } /** @@ -126,30 +153,29 @@ abstract public function execute($ids); * * @param array $processIds * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @deprecated Used only for backward compatibility for indexer, which not support indexation by dimensions */ protected function _syncData(array $processIds = []) { - // delete invalid rows - $select = $this->_connection->select()->from( - ['index_price' => $this->getIndexTargetTable()], - null - )->joinLeft( - ['ip_tmp' => $this->_defaultIndexerResource->getIdxTable()], - 'index_price.entity_id = ip_tmp.entity_id AND index_price.website_id = ip_tmp.website_id', - [] - )->where( - 'ip_tmp.entity_id IS NULL' - ); - if (!empty($processIds)) { - $select->where('index_price.entity_id IN(?)', $processIds); - } - $sql = $select->deleteFromSelect('index_price'); - $this->_connection->query($sql); + // for backward compatibility split data from old idx table on dimension tables + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $insertSelect = $this->getConnection()->select()->from( + ['ip_tmp' => $this->_defaultIndexerResource->getIdxTable()] + ); - $this->_insertFromTable( - $this->_defaultIndexerResource->getIdxTable(), - $this->getIndexTargetTable() - ); + foreach ($dimensions as $dimension) { + if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) { + $insertSelect->where('ip_tmp.website_id = ?', $dimension->getValue()); + } + if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) { + $insertSelect->where('ip_tmp.customer_group_id = ?', $dimension->getValue()); + } + } + + $query = $insertSelect->insertFromSelect($this->tableMaintainer->getMainTable($dimensions)); + $this->getConnection()->query($query); + } return $this; } @@ -157,12 +183,15 @@ protected function _syncData(array $processIds = []) * Prepare website current dates table * * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _prepareWebsiteDateTable() { $baseCurrency = $this->_config->getValue(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE); - $select = $this->_connection->select()->from( + $select = $this->getConnection()->select()->from( ['cw' => $this->_defaultIndexerResource->getTable('store_website')], ['website_id'] )->join( @@ -174,7 +203,7 @@ protected function _prepareWebsiteDateTable() ); $data = []; - foreach ($this->_connection->fetchAll($select) as $item) { + foreach ($this->getConnection()->fetchAll($select) as $item) { /** @var $website \Magento\Store\Model\Website */ $website = $this->_storeManager->getWebsite($item['website_id']); @@ -199,6 +228,7 @@ protected function _prepareWebsiteDateTable() 'website_id' => $website->getId(), 'website_date' => $this->_dateTime->formatDate($timestamp, false), 'rate' => $rate, + 'default_store_id' => $store->getId() ]; } } @@ -207,7 +237,7 @@ protected function _prepareWebsiteDateTable() $this->_emptyTable($table); if ($data) { foreach ($data as $row) { - $this->_connection->insertOnDuplicate($table, $row, array_keys($row)); + $this->getConnection()->insertOnDuplicate($table, $row, array_keys($row)); } } @@ -230,9 +260,13 @@ protected function _prepareTierPriceIndex($entityIds = null) /** * Retrieve price indexers per product type * + * @param bool $fullReindexAction + * * @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface[] + * + * @throws \Magento\Framework\Exception\LocalizedException */ - public function getTypeIndexers() + public function getTypeIndexers($fullReindexAction = false) { if ($this->_indexers === null) { $this->_indexers = []; @@ -242,14 +276,20 @@ public function getTypeIndexers() $typeInfo['price_indexer'] ) ? $typeInfo['price_indexer'] : get_class($this->_defaultIndexerResource); - $isComposite = !empty($typeInfo['composite']); $indexer = $this->_indexerPriceFactory->create( - $modelName - )->setTypeId( - $typeId - )->setIsComposite( - $isComposite + $modelName, + [ + 'fullReindexAction' => $fullReindexAction + ] ); + // left setters for backward compatibility + if ($indexer instanceof DefaultPrice) { + $indexer->setTypeId( + $typeId + )->setIsComposite( + !empty($typeInfo['composite']) + ); + } $this->_indexers[$typeId] = $indexer; } } @@ -262,7 +302,9 @@ public function getTypeIndexers() * * @param string $productTypeId * @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface + * * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getIndexer($productTypeId) { @@ -283,19 +325,19 @@ protected function _getIndexer($productTypeId) */ protected function _insertFromTable($sourceTable, $destTable, $where = null) { - $sourceColumns = array_keys($this->_connection->describeTable($sourceTable)); - $targetColumns = array_keys($this->_connection->describeTable($destTable)); - $select = $this->_connection->select()->from($sourceTable, $sourceColumns); + $sourceColumns = array_keys($this->getConnection()->describeTable($sourceTable)); + $targetColumns = array_keys($this->getConnection()->describeTable($destTable)); + $select = $this->getConnection()->select()->from($sourceTable, $sourceColumns); if ($where) { $select->where($where); } - $query = $this->_connection->insertFromSelect( + $query = $this->getConnection()->insertFromSelect( $select, $destTable, $targetColumns, \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE ); - $this->_connection->query($query); + $this->getConnection()->query($query); } /** @@ -306,7 +348,7 @@ protected function _insertFromTable($sourceTable, $destTable, $where = null) */ protected function _emptyTable($table) { - $this->_connection->delete($table); + $this->getConnection()->delete($table); } /** @@ -314,46 +356,64 @@ protected function _emptyTable($table) * * @param array $changedIds * @return array Affected ids + * + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _reindexRows($changedIds = []) { - $this->_emptyTable($this->_defaultIndexerResource->getIdxTable()); $this->_prepareWebsiteDateTable(); $productsTypes = $this->getProductsTypes($changedIds); - $compositeIds = []; - $notCompositeIds = []; + $parentProductsTypes = $this->getParentProductsTypes($changedIds); + $changedIds = array_merge($changedIds, ...array_values($parentProductsTypes)); + $productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes); + + if ($changedIds) { + $this->deleteIndexData($changedIds); + } foreach ($productsTypes as $productType => $entityIds) { $indexer = $this->_getIndexer($productType); - if ($indexer->getIsComposite()) { - $compositeIds += $entityIds; + if ($indexer instanceof DimensionalIndexerInterface) { + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $this->tableMaintainer->createMainTmpTable($dimensions); + $temporaryTable = $this->tableMaintainer->getMainTmpTable($dimensions); + $this->_emptyTable($temporaryTable); + $indexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false)); + // copy to index + $this->_insertFromTable( + $temporaryTable, + $this->tableMaintainer->getMainTable($dimensions) + ); + } } else { - $notCompositeIds += $entityIds; + // handle 3d-party indexers for backward compatibility + $this->_emptyTable($this->_defaultIndexerResource->getIdxTable()); + $this->_copyRelationIndexData($entityIds); + $indexer->reindexEntity($entityIds); + $this->_syncData($entityIds); } } - if (!empty($notCompositeIds)) { - $parentProductsTypes = $this->getParentProductsTypes($notCompositeIds); - $productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes); - foreach ($parentProductsTypes as $parentProductsIds) { - $compositeIds = $compositeIds + $parentProductsIds; - $changedIds = array_merge($changedIds, $parentProductsIds); - } - } - - if (!empty($compositeIds)) { - $this->_copyRelationIndexData($compositeIds, $notCompositeIds); - } - $this->_prepareTierPriceIndex($compositeIds + $notCompositeIds); + return $changedIds; + } - foreach ($productsTypes as $productType => $entityIds) { - $indexer = $this->_getIndexer($productType); - $indexer->reindexEntity($entityIds); + /** + * @param array $entityIds + * @return void + */ + private function deleteIndexData(array $entityIds) + { + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $select = $this->getConnection()->select()->from( + ['index_price' => $this->tableMaintainer->getMainTable($dimensions)], + null + )->where('index_price.entity_id IN (?)', $entityIds); + $query = $select->deleteFromSelect('index_price'); + $this->getConnection()->query($query); } - $this->_syncData($changedIds); - - return $compositeIds + $notCompositeIds; } /** @@ -362,11 +422,15 @@ protected function _reindexRows($changedIds = []) * @param null|array $parentIds * @param array $excludeIds * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * @deprecated Used only for backward compatibility for do not broke custom indexer implementation + * which do not work by dimensions. + * For indexers, which support dimensions all composite products read data directly from main price indexer table + * or replica table for partial or full reindex correspondingly. */ protected function _copyRelationIndexData($parentIds, $excludeIds = null) { $linkField = $this->getProductIdFieldName(); - $select = $this->_connection->select()->from( + $select = $this->getConnection()->select()->from( $this->_defaultIndexerResource->getTable('catalog_product_relation'), ['child_id'] )->join( @@ -381,22 +445,45 @@ protected function _copyRelationIndexData($parentIds, $excludeIds = null) $select->where('child_id NOT IN(?)', $excludeIds); } - $children = $this->_connection->fetchCol($select); + $children = $this->getConnection()->fetchCol($select); if ($children) { - $select = $this->_connection->select()->from( - $this->getIndexTargetTable() - )->where( - 'entity_id IN(?)', - $children - ); - $query = $select->insertFromSelect($this->_defaultIndexerResource->getIdxTable(), [], false); - $this->_connection->query($query); + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $select = $this->getConnection()->select()->from( + $this->getIndexTargetTableByDimension($dimensions) + )->where( + 'entity_id IN(?)', + $children + ); + $query = $select->insertFromSelect($this->_defaultIndexerResource->getIdxTable(), [], false); + $this->getConnection()->query($query); + } } return $this; } + /** + * Retrieve index table by dimension that will be used for write operations. + * + * This method is used during both partial and full reindex to identify the table. + * + * @param \Magento\Framework\Search\Request\Dimension[] $dimensions + * + * @return string + */ + private function getIndexTargetTableByDimension(array $dimensions) + { + $indexTargetTable = $this->getIndexTargetTable(); + if ($indexTargetTable === self::getIndexTargetTable()) { + $indexTargetTable = $this->tableMaintainer->getMainTable($dimensions); + } + if ($indexTargetTable === self::getIndexTargetTable() . '_replica') { + $indexTargetTable = $this->tableMaintainer->getMainReplicaTable($dimensions); + } + return $indexTargetTable; + } + /** * Retrieve index table that will be used for write operations. * @@ -415,8 +502,8 @@ protected function getIndexTargetTable() protected function getProductIdFieldName() { $table = $this->_defaultIndexerResource->getTable('catalog_product_entity'); - $indexList = $this->_connection->getIndexList($table); - return $indexList[$this->_connection->getPrimaryKeyName($table)]['COLUMNS_LIST'][0]; + $indexList = $this->getConnection()->getIndexList($table); + return $indexList[$this->getConnection()->getPrimaryKeyName($table)]['COLUMNS_LIST'][0]; } /** @@ -427,14 +514,14 @@ protected function getProductIdFieldName() */ private function getProductsTypes(array $changedIds = []) { - $select = $this->_connection->select()->from( + $select = $this->getConnection()->select()->from( $this->_defaultIndexerResource->getTable('catalog_product_entity'), ['entity_id', 'type_id'] ); if ($changedIds) { $select->where('entity_id IN (?)', $changedIds); } - $pairs = $this->_connection->fetchPairs($select); + $pairs = $this->getConnection()->fetchPairs($select); $byType = []; foreach ($pairs as $productId => $productType) { @@ -445,14 +532,15 @@ private function getProductsTypes(array $changedIds = []) } /** - * Get parent products types. + * Get parent products types + * Used for add composite products to reindex if we have only simple products in changed ids set * * @param array $productsIds * @return array */ private function getParentProductsTypes(array $productsIds) { - $select = $this->_connection->select()->from( + $select = $this->getConnection()->select()->from( ['l' => $this->_defaultIndexerResource->getTable('catalog_product_relation')], '' )->join( @@ -463,7 +551,7 @@ private function getParentProductsTypes(array $productsIds) 'l.child_id IN(?)', $productsIds ); - $pairs = $this->_connection->fetchPairs($select); + $pairs = $this->getConnection()->fetchPairs($select); $byType = []; foreach ($pairs as $productId => $productType) { @@ -472,4 +560,14 @@ private function getParentProductsTypes(array $productsIds) return $byType; } + + /** + * Get connection + * + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + return $this->_defaultIndexerResource->getConnection(); + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php index ba04af8ec1f41..1a75751570658 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php @@ -6,9 +6,17 @@ namespace Magento\Catalog\Model\Indexer\Product\Price\Action; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; /** * Class Full reindex action + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction @@ -33,6 +41,26 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction */ private $activeTableSwitcher; + /** + * @var EntityMetadataInterface + */ + private $productMetaDataCached; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory + */ + private $dimensionCollectionFactory; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer + */ + private $dimensionTableMaintainer; + + /** + * @var \Magento\Indexer\Model\ProcessManager + */ + private $processManager; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $config * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -46,7 +74,9 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator|null $batchSizeCalculator * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher - * + * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory|null $dimensionCollectionFactory + * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|null $dimensionTableMaintainer + * @param \Magento\Indexer\Model\ProcessManager $processManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -61,7 +91,10 @@ public function __construct( \Magento\Framework\EntityManager\MetadataPool $metadataPool = null, \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator $batchSizeCalculator = null, \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null, - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null + \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null, + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null, + \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $dimensionTableMaintainer = null, + \Magento\Indexer\Model\ProcessManager $processManager = null ) { parent::__construct( $config, @@ -85,6 +118,15 @@ public function __construct( $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get( \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class ); + $this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + ); + $this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class + ); + $this->processManager = $processManager ?: ObjectManager::getInstance()->get( + \Magento\Indexer\Model\ProcessManager::class + ); } /** @@ -92,75 +134,335 @@ public function __construct( * * @param array|int|null $ids * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Exception * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($ids = null) { try { - $this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false); - $this->_prepareWebsiteDateTable(); + //Prepare indexer tables before full reindex + $this->prepareTables(); + + /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */ + foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) { + if ($priceIndexer instanceof DimensionalIndexerInterface) { + //New price reindex mechanism + $this->reindexProductTypeWithDimensions($priceIndexer, $typeId); + continue; + } + + $priceIndexer->getTableStrategy()->setUseIdxTable(false); + + //Old price reindex mechanism + $this->reindexProductType($priceIndexer, $typeId); + } + + //Final replacement of tables from replica to main + $this->switchTables(); + } catch (\Exception $e) { + throw new LocalizedException(__($e->getMessage()), $e); + } + } + + /** + * Prepare indexer tables before full reindex + * + * @return void + * @throws \Exception + */ + private function prepareTables() + { + $this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false); + + $this->_prepareWebsiteDateTable(); + + $this->truncateReplicaTables(); + } + + /** + * Truncate replica tables by dimensions + * + * @return void + * @throws \Exception + */ + private function truncateReplicaTables() + { + foreach ($this->dimensionCollectionFactory->create() as $dimension) { + $dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension); + $this->_defaultIndexerResource->getConnection()->truncateTable($dimensionTable); + } + } + + /** + * Reindex new 'Dimensional' price indexer by product type + * + * @param DimensionalIndexerInterface $priceIndexer + * @param string $typeId + * + * @return void + * @throws \Exception + */ + private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId) + { + $userFunctions = []; + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) { + return $this->reindexByBatches($priceIndexer, $dimensions, $typeId); + }; + } + $this->processManager->execute($userFunctions); + } + + /** + * Reindex new 'Dimensional' price indexer by batches + * + * @param DimensionalIndexerInterface $priceIndexer + * @param array $dimensions + * @param string $typeId + * + * @return void + * @throws \Exception + */ + private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, array $dimensions, string $typeId) + { + foreach ($this->getBatchesForIndexer($typeId) as $batch) { + $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions, $typeId); + } + } + + /** + * Get batches for new 'Dimensional' price indexer + * + * @param string $typeId + * + * @return \Generator + * @throws \Exception + */ + private function getBatchesForIndexer(string $typeId) + { + $connection = $this->_defaultIndexerResource->getConnection(); + return $this->batchProvider->getBatches( + $connection, + $this->getProductMetaData()->getEntityTable(), + $this->getProductMetaData()->getIdentifierField(), + $this->batchSizeCalculator->estimateBatchSize( + $connection, + $typeId + ) + ); + } + + /** + * Reindex by batch for new 'Dimensional' price indexer + * + * @param DimensionalIndexerInterface $priceIndexer + * @param array $batch + * @param array $dimensions + * @param string $typeId + * + * @return void + * @throws \Exception + */ + private function reindexByBatchWithDimensions( + DimensionalIndexerInterface $priceIndexer, + array $batch, + array $dimensions, + string $typeId + ) { + $entityIds = $this->getEntityIdsFromBatch($typeId, $batch); + + if (!empty($entityIds)) { + $this->dimensionTableMaintainer->createMainTmpTable($dimensions); + $temporaryTable = $this->dimensionTableMaintainer->getMainTmpTable($dimensions); + $this->_emptyTable($temporaryTable); + + $priceIndexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false)); - $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); - $replicaTable = $this->activeTableSwitcher->getAdditionalTableName( - $this->_defaultIndexerResource->getMainTable() + // Sync data from temp table to index table + $this->_insertFromTable( + $temporaryTable, + $this->dimensionTableMaintainer->getMainReplicaTable($dimensions) ); + } + } - // Prepare replica table for indexation. - $this->_defaultIndexerResource->getConnection()->truncateTable($replicaTable); + /** + * Reindex old price indexer by product type + * + * @param PriceInterface $priceIndexer + * @param string $typeId + * + * @return void + * @throws \Exception + */ + private function reindexProductType(PriceInterface $priceIndexer, string $typeId) + { + foreach ($this->getBatchesForIndexer($typeId) as $batch) { + $this->reindexBatch($priceIndexer, $batch, $typeId); + } + } - /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */ - foreach ($this->getTypeIndexers() as $indexer) { - $indexer->getTableStrategy()->setUseIdxTable(false); - $connection = $indexer->getConnection(); - - $batches = $this->batchProvider->getBatches( - $connection, - $entityMetadata->getEntityTable(), - $entityMetadata->getIdentifierField(), - $this->batchSizeCalculator->estimateBatchSize($connection, $indexer->getTypeId()) - ); - - foreach ($batches as $batch) { - // Get entity ids from batch - $select = $connection->select(); - $select->distinct(true); - $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField()); - $select->where('type_id = ?', $indexer->getTypeId()); - - $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch); - - if (!empty($entityIds)) { - // Temporary table will created if not exists - $idxTableName = $this->_defaultIndexerResource->getIdxTable(); - $this->_emptyTable($idxTableName); - - if ($indexer->getIsComposite()) { - $this->_copyRelationIndexData($entityIds); - } - $this->_prepareTierPriceIndex($entityIds); - - // Reindex entities by id - $indexer->reindexEntity($entityIds); - - // Sync data from temp table to index table - $this->_insertFromTable($idxTableName, $replicaTable); - - // Drop temporary index table - $connection->dropTable($idxTableName); - } - } + /** + * Reindex by batch for old price indexer + * + * @param PriceInterface $priceIndexer + * @param array $batch + * @param string $typeId + * + * @return void + * @throws \Exception + */ + private function reindexBatch(PriceInterface $priceIndexer, array $batch, string $typeId) + { + $entityIds = $this->getEntityIdsFromBatch($typeId, $batch); + + if (!empty($entityIds)) { + // Temporary table will created if not exists + $idxTableName = $this->_defaultIndexerResource->getIdxTable(); + $this->_emptyTable($idxTableName); + + if ($priceIndexer->getIsComposite()) { + $this->_copyRelationIndexData($entityIds); } + + // Reindex entities by id + $priceIndexer->reindexEntity($entityIds); + + // Sync data from temp table to index table + $this->_insertFromTable($idxTableName, $this->getReplicaTable()); + + // Drop temporary index table + $this->_defaultIndexerResource->getConnection()->dropTable($idxTableName); + } + } + + /** + * Get Entity Ids from batch + * + * @param string $typeId + * @param array $batch + * + * @return array + * @throws \Exception + */ + private function getEntityIdsFromBatch(string $typeId, array $batch) + { + $connection = $this->_defaultIndexerResource->getConnection(); + + // Get entity ids from batch + $select = $connection + ->select() + ->distinct(true) + ->from( + ['e' => $this->getProductMetaData()->getEntityTable()], + $this->getProductMetaData()->getIdentifierField() + ) + ->where('type_id = ?', $typeId); + + return $this->batchProvider->getBatchIds($connection, $select, $batch); + } + + /** + * Get product meta data + * + * @return EntityMetadataInterface + * @throws \Exception + */ + private function getProductMetaData() + { + if ($this->productMetaDataCached === null) { + $this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class); + } + + return $this->productMetaDataCached; + } + + /** + * Get replica table + * + * @return string + * @throws \Exception + */ + private function getReplicaTable() + { + return $this->activeTableSwitcher->getAdditionalTableName( + $this->_defaultIndexerResource->getMainTable() + ); + } + + /** + * Replacement of tables from replica to main + * + * @return void + */ + private function switchTables() + { + // Switch dimension tables + $mainTablesByDimension = []; + + foreach ($this->dimensionCollectionFactory->create() as $dimensions) { + $mainTablesByDimension[] = $this->dimensionTableMaintainer->getMainTable($dimensions); + + //Move data from indexers with old realisation + $this->moveDataFromReplicaTableToReplicaTables($dimensions); + } + + if (count($mainTablesByDimension) > 0) { $this->activeTableSwitcher->switchTable( $this->_defaultIndexerResource->getConnection(), - [$this->_defaultIndexerResource->getMainTable()] + $mainTablesByDimension ); - } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e); } } /** + * Move data from old price indexer mechanism to new indexer mechanism by dimensions. + * Used only for backward compatibility + * + * @param array $dimensions + * + * @return void + */ + private function moveDataFromReplicaTableToReplicaTables(array $dimensions) + { + if (!$dimensions) { + return; + } + $select = $this->dimensionTableMaintainer->getConnection()->select()->from( + $this->dimensionTableMaintainer->getMainReplicaTable([]) + ); + + $check = clone $select; + $check->reset('columns')->columns('count(*)'); + + if (!$this->dimensionTableMaintainer->getConnection()->query($check)->fetchColumn()) { + return; + } + + $replicaTablesByDimension = $this->dimensionTableMaintainer->getMainReplicaTable($dimensions); + + foreach ($dimensions as $dimension) { + if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) { + $select->where('website_id = ?', $dimension->getValue()); + } + if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) { + $select->where('customer_group_id = ?', $dimension->getValue()); + } + } + + $this->dimensionTableMaintainer->getConnection()->query( + $this->dimensionTableMaintainer->getConnection()->insertFromSelect( + $select, + $replicaTablesByDimension, + [], + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ) + ); + } + + /** + * @deprecated + * * @inheritdoc */ protected function getIndexTargetTable() diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php new file mode 100644 index 0000000000000..31459af81ccc7 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\Indexer\DimensionProviderInterface; +use Magento\Framework\Indexer\MultiDimensionProvider; + +class DimensionCollectionFactory +{ + /** + * @var \Magento\Framework\Indexer\MultiDimensionProviderFactory + */ + private $multiDimensionProviderFactory; + + /** + * @var DimensionProviderInterface[] + */ + private $dimensionProviders; + + /** + * @var DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @param \Magento\Framework\Indexer\MultiDimensionProviderFactory $multiDimensionProviderFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + * @param array $dimensionProviders + */ + public function __construct( + \Magento\Framework\Indexer\MultiDimensionProviderFactory $multiDimensionProviderFactory, + DimensionModeConfiguration $dimensionModeConfiguration, + array $dimensionProviders + ) { + $this->multiDimensionProviderFactory = $multiDimensionProviderFactory; + $this->dimensionProviders = $dimensionProviders; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + } + + /** + * Create MultiDimensionProvider for specified "dimension mode". + * By default return multiplication of dimensions by current set mode + * + * @param string|null $dimensionsMode + * @return MultiDimensionProvider + */ + public function create(string $dimensionsMode = null): MultiDimensionProvider + { + $dimensionConfiguration = $this->dimensionModeConfiguration->getDimensionConfiguration($dimensionsMode); + + $providers = []; + foreach ($dimensionConfiguration as $dimensionName) { + if (!isset($this->dimensionProviders[$dimensionName])) { + throw new \LogicException( + 'Dimension Provider is missing. Cannot handle unknown dimension: ' . $dimensionName + ); + } + $providers[] = clone $this->dimensionProviders[$dimensionName]; + } + + return $this->multiDimensionProviderFactory->create( + [ + 'dimensionProviders' => $providers + ] + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php new file mode 100644 index 0000000000000..7a4d8e313462d --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; + +class DimensionModeConfiguration +{ + /**#@+ + * Available modes of dimensions for product price indexer + */ + const DIMENSION_NONE = 'none'; + const DIMENSION_WEBSITE = 'website'; + const DIMENSION_CUSTOMER_GROUP = 'customer_group'; + const DIMENSION_WEBSITE_AND_CUSTOMER_GROUP = 'website_and_customer_group'; + /**#@-*/ + + /** + * Mapping between dimension mode and dimension provider name + * + * @var array + */ + private $modesMapping = [ + self::DIMENSION_NONE => [ + ], + self::DIMENSION_WEBSITE => [ + WebsiteDimensionProvider::DIMENSION_NAME + ], + self::DIMENSION_CUSTOMER_GROUP => [ + CustomerGroupDimensionProvider::DIMENSION_NAME + ], + self::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP => [ + WebsiteDimensionProvider::DIMENSION_NAME, + CustomerGroupDimensionProvider::DIMENSION_NAME + ], + ]; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var string + */ + private $currentMode; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * Return dimension modes configuration. + * + * @return array + */ + public function getDimensionModes(): array + { + return $this->modesMapping; + } + + /** + * Get names of dimensions which used for provided mode. + * By default return dimensions for current enabled mode + * + * @param string|null $mode + * @return string[] + * @throws \InvalidArgumentException + */ + public function getDimensionConfiguration(string $mode = null): array + { + if ($mode && !isset($this->modesMapping[$mode])) { + throw new \InvalidArgumentException( + sprintf('Undefined dimension mode "%s".', $mode) + ); + } + return $this->modesMapping[$mode ?? $this->getCurrentMode()]; + } + + /** + * @return string + */ + private function getCurrentMode(): string + { + if (null === $this->currentMode) { + $this->currentMode = $this->scopeConfig->getValue(ModeSwitcherConfiguration::XML_PATH_PRICE_DIMENSIONS_MODE) + ?: self::DIMENSION_NONE; + } + + return $this->currentMode; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php new file mode 100644 index 0000000000000..c418f2e1f253b --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php @@ -0,0 +1,211 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\Search\Request\Dimension; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Indexer\Model\DimensionModes; +use Magento\Indexer\Model\DimensionMode; + +/** + * Class to prepare new tables for new indexer mode + */ +class ModeSwitcher implements \Magento\Indexer\Model\ModeSwitcherInterface +{ + /** + * TableMaintainer + * + * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer + */ + private $tableMaintainer; + + /** + * DimensionCollectionFactory + * + * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory + */ + private $dimensionCollectionFactory; + + /** + * @var array|null + */ + private $dimensionsArray; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @var ModeSwitcherConfiguration + */ + private $modeSwitcherConfiguration; + + /** + * @param TableMaintainer $tableMaintainer + * @param DimensionCollectionFactory $dimensionCollectionFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + * @param ModeSwitcherConfiguration $modeSwitcherConfiguration + */ + public function __construct( + TableMaintainer $tableMaintainer, + DimensionCollectionFactory $dimensionCollectionFactory, + DimensionModeConfiguration $dimensionModeConfiguration, + ModeSwitcherConfiguration $modeSwitcherConfiguration + ) { + $this->tableMaintainer = $tableMaintainer; + $this->dimensionCollectionFactory = $dimensionCollectionFactory; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + $this->modeSwitcherConfiguration = $modeSwitcherConfiguration; + } + + /** + * @inheritdoc + */ + public function getDimensionModes(): DimensionModes + { + $dimensionsList = []; + foreach ($this->dimensionModeConfiguration->getDimensionModes() as $dimension => $modes) { + $dimensionsList[] = new DimensionMode($dimension, $modes); + } + + return new DimensionModes($dimensionsList); + } + + /** + * @inheritdoc + */ + public function switchMode(string $currentMode, string $previousMode) + { + //Create new tables and move data + $this->createTables($currentMode); + $this->moveData($currentMode, $previousMode); + + //Change config options + $this->modeSwitcherConfiguration->saveMode($currentMode); + + //Delete old tables + $this->dropTables($previousMode); + } + + /** + * Create new tables + * + * @param string $currentMode + * + * @return void + * @throws \Zend_Db_Exception + */ + public function createTables(string $currentMode) + { + foreach ($this->getDimensionsArray($currentMode) as $dimensions) { + if (!empty($dimensions)) { + $this->tableMaintainer->createTablesForDimensions($dimensions); + } + } + } + + /** + * Move data from old tables to new + * + * @param string $currentMode + * @param string $previousMode + * + * @return void + */ + public function moveData(string $currentMode, string $previousMode) + { + $dimensionsArrayForCurrentMode = $this->getDimensionsArray($currentMode); + $dimensionsArrayForPreviousMode = $this->getDimensionsArray($previousMode); + + foreach ($dimensionsArrayForCurrentMode as $dimensionsForCurrentMode) { + $newTable = $this->tableMaintainer->getMainTable($dimensionsForCurrentMode); + if (empty($dimensionsForCurrentMode)) { + // new mode is 'none' + foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) { + $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode); + $this->insertFromOldTablesToNew($newTable, $oldTable); + } + } else { + // new mode is not 'none' + foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) { + $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode); + $this->insertFromOldTablesToNew($newTable, $oldTable, $dimensionsForCurrentMode); + } + } + } + } + + /** + * Drop old tables + * + * @param string $previousMode + * + * @return void + */ + public function dropTables(string $previousMode) + { + foreach ($this->getDimensionsArray($previousMode) as $dimensions) { + if (empty($dimensions)) { + $this->tableMaintainer->truncateTablesForDimensions($dimensions); + } else { + $this->tableMaintainer->dropTablesForDimensions($dimensions); + } + } + } + + /** + * Get dimensions array + * + * @param string $mode + * + * @return \Magento\Framework\Indexer\MultiDimensionProvider + */ + private function getDimensionsArray(string $mode): \Magento\Framework\Indexer\MultiDimensionProvider + { + if (isset($this->dimensionsArray[$mode])) { + return $this->dimensionsArray[$mode]; + } + + $this->dimensionsArray[$mode] = $this->dimensionCollectionFactory->create($mode); + + return $this->dimensionsArray[$mode]; + } + + /** + * Insert from old tables data to new + * + * @param string $newTable + * @param string $oldTable + * @param Dimension[] $dimensions + * + * @return void + */ + private function insertFromOldTablesToNew(string $newTable, string $oldTable, array $dimensions = []) + { + $select = $this->tableMaintainer->getConnection()->select()->from($oldTable); + + foreach ($dimensions as $dimension) { + if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) { + $select->where('website_id = ?', $dimension->getValue()); + } + if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) { + $select->where('customer_group_id = ?', $dimension->getValue()); + } + } + $this->tableMaintainer->getConnection()->query( + $this->tableMaintainer->getConnection()->insertFromSelect( + $select, + $newTable, + [], + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ) + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php new file mode 100644 index 0000000000000..ae00ec51f2960 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\App\Config\ConfigResource\ConfigInterface; +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Indexer\Model\Indexer; + +/** + * Class to configure indexers and system config after modes has been switched + */ +class ModeSwitcherConfiguration +{ + const XML_PATH_PRICE_DIMENSIONS_MODE = 'indexer/catalog_product_price/dimensions_mode'; + + /** + * ConfigInterface + * + * @var ConfigInterface + */ + private $configWriter; + + /** + * TypeListInterface + * + * @var TypeListInterface + */ + private $cacheTypeList; + + /** + * @var Indexer $indexer + */ + private $indexer; + + /** + * @param ConfigInterface $configWriter + * @param TypeListInterface $cacheTypeList + * @param Indexer $indexer + */ + public function __construct( + ConfigInterface $configWriter, + TypeListInterface $cacheTypeList, + Indexer $indexer + ) { + $this->configWriter = $configWriter; + $this->cacheTypeList = $cacheTypeList; + $this->indexer = $indexer; + } + + /** + * Save switcher mode and invalidate reindex. + * + * @param string $mode + * @return void + * @throws \InvalidArgumentException + */ + public function saveMode(string $mode) + { + //Change config options + $this->configWriter->saveConfig(self::XML_PATH_PRICE_DIMENSIONS_MODE, $mode); + $this->cacheTypeList->cleanType('config'); + $this->indexer->load(\Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID); + $this->indexer->invalidate(); + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php index 32b2db8a7008c..9b99ee8c8dc8c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php @@ -5,9 +5,15 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Price\Plugin; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Customer\Api\Data\GroupInterface; -use \Magento\Catalog\Model\Indexer\Product\Price\UpdateIndexInterface; +use Magento\Catalog\Model\Indexer\Product\Price\UpdateIndexInterface; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Framework\Indexer\Dimension; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; class CustomerGroup { @@ -17,14 +23,46 @@ class CustomerGroup private $updateIndex; /** - * Constructor + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * DimensionFactory * + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @var DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @var WebsiteDimensionProvider + */ + private $websiteDimensionProvider; + + /** * @param UpdateIndexInterface $updateIndex + * @param TableMaintainer $tableMaintainer + * @param DimensionFactory $dimensionFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + * @param WebsiteDimensionProvider $websiteDimensionProvider */ public function __construct( - UpdateIndexInterface $updateIndex + UpdateIndexInterface $updateIndex, + TableMaintainer $tableMaintainer, + DimensionFactory $dimensionFactory, + DimensionModeConfiguration $dimensionModeConfiguration, + WebsiteDimensionProvider $websiteDimensionProvider ) { $this->updateIndex = $updateIndex; + $this->tableMaintainer = $tableMaintainer; + $this->dimensionFactory = $dimensionFactory; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + $this->websiteDimensionProvider = $websiteDimensionProvider; } /** @@ -32,7 +70,8 @@ public function __construct( * * @param GroupRepositoryInterface $subject * @param \Closure $proceed - * @param GroupInterface $result + * @param GroupInterface $group + * * @return GroupInterface * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -43,7 +82,64 @@ public function aroundSave( ) { $isGroupNew = !$group->getId(); $group = $proceed($group); + if ($isGroupNew) { + foreach ($this->getAffectedDimensions((string)$group->getId()) as $dimensions) { + $this->tableMaintainer->createTablesForDimensions($dimensions); + } + } $this->updateIndex->update($group, $isGroupNew); return $group; } + + /** + * Update price index after customer group deleted + * + * @param GroupRepositoryInterface $subject + * @param bool $result + * @param string $groupId + * + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDeleteById(GroupRepositoryInterface $subject, bool $result, string $groupId) + { + foreach ($this->getAffectedDimensions($groupId) as $dimensions) { + $this->tableMaintainer->dropTablesForDimensions($dimensions); + } + + return $result; + } + + /** + * Get affected dimensions + * + * @param string $groupId + * @return Dimension[][] + */ + private function getAffectedDimensions(string $groupId): array + { + $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration(); + // do not return dimensions if Customer Group dimension is not present in configuration + if (!in_array(CustomerGroupDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) { + return []; + } + $customerGroupDimension = $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + $groupId + ); + + $dimensions = []; + if (in_array(WebsiteDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) { + foreach ($this->websiteDimensionProvider as $websiteDimension) { + $dimensions[] = [ + $customerGroupDimension, + $websiteDimension + ]; + } + } else { + $dimensions[] = [$customerGroupDimension]; + } + + return $dimensions; + } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php new file mode 100644 index 0000000000000..fbeec22783090 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php @@ -0,0 +1,140 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price\Plugin; + +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; +use Magento\Framework\App\Http\Context; +use Magento\Framework\Indexer\Dimension; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Customer\Model\Context as CustomerContext; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; + +/** + * Replace catalog_product_index_price table name on the table name segmented per dimension. + * Used only for backward compatibility + */ +class TableResolver +{ + /** + * @var IndexScopeResolverInterface + */ + private $priceTableResolver; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var Context + */ + private $httpContext; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @var DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @param IndexScopeResolverInterface $priceTableResolver + * @param StoreManagerInterface $storeManager + * @param Context $context + * @param DimensionFactory $dimensionFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + */ + public function __construct( + IndexScopeResolverInterface $priceTableResolver, + StoreManagerInterface $storeManager, + Context $context, + DimensionFactory $dimensionFactory, + DimensionModeConfiguration $dimensionModeConfiguration + ) { + $this->priceTableResolver = $priceTableResolver; + $this->storeManager = $storeManager; + $this->httpContext = $context; + $this->dimensionFactory = $dimensionFactory; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + } + + /** + * Replacing catalog_product_index_price table name on the table name segmented per dimension. + * + * @param ResourceConnection $subject + * @param string $result + * @param string|string[] $tableName + * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetTableName( + ResourceConnection $subject, + string $result, + $tableName + ) { + if (!is_array($tableName) + && $tableName === 'catalog_product_index_price' + && $this->dimensionModeConfiguration->getDimensionConfiguration() + ) { + return $this->priceTableResolver->resolve('catalog_product_index_price', $this->getDimensions()); + } + + return $result; + } + + /** + * @return Dimension[] + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getDimensions(): array + { + $dimensions = []; + foreach ($this->dimensionModeConfiguration->getDimensionConfiguration() as $dimensionName) { + if ($dimensionName === WebsiteDimensionProvider::DIMENSION_NAME) { + $dimensions[] = $this->createDimensionFromWebsite(); + } + if ($dimensionName === CustomerGroupDimensionProvider::DIMENSION_NAME) { + $dimensions[] = $this->createDimensionFromCustomerGroup(); + } + } + + return $dimensions; + } + + /** + * @return Dimension + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function createDimensionFromWebsite(): Dimension + { + $storeKey = $this->httpContext->getValue(StoreManagerInterface::CONTEXT_STORE); + return $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$this->storeManager->getStore($storeKey)->getWebsiteId() + ); + } + + /** + * @return Dimension + */ + private function createDimensionFromCustomerGroup(): Dimension + { + return $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$this->httpContext->getValue(CustomerContext::CONTEXT_GROUP) + ); + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php index 269515e292e17..4831680f07c33 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php @@ -5,33 +5,128 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Price\Plugin; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Framework\Indexer\Dimension; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\AbstractModel; + class Website { /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * DimensionFactory + * + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @var DimensionModeConfiguration */ - protected $_processor; + private $dimensionModeConfiguration; /** - * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $processor + * @var CustomerGroupDimensionProvider */ - public function __construct(\Magento\Catalog\Model\Indexer\Product\Price\Processor $processor) + private $customerGroupDimensionProvider; + + /** + * @param TableMaintainer $tableMaintainer + * @param DimensionFactory $dimensionFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + * @param CustomerGroupDimensionProvider $customerGroupDimensionProvider + */ + public function __construct( + TableMaintainer $tableMaintainer, + DimensionFactory $dimensionFactory, + DimensionModeConfiguration $dimensionModeConfiguration, + CustomerGroupDimensionProvider $customerGroupDimensionProvider + ) { + $this->tableMaintainer = $tableMaintainer; + $this->dimensionFactory = $dimensionFactory; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + $this->customerGroupDimensionProvider = $customerGroupDimensionProvider; + } + + /** + * Update price index after website deleted + * + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $website + * + * @return AbstractDb + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website) { - $this->_processor = $processor; + foreach ($this->getAffectedDimensions($website->getId()) as $dimensions) { + $this->tableMaintainer->dropTablesForDimensions($dimensions); + } + + return $objectResource; } /** - * Invalidate price indexer + * Update price index after website created * - * @param \Magento\Store\Model\ResourceModel\Website $subject - * @param \Magento\Store\Model\ResourceModel\Website $result - * @return \Magento\Store\Model\ResourceModel\Website + * @param AbstractDb $subject + * @param AbstractDb $objectResource + * @param AbstractModel $website * + * @return AbstractDb * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterDelete(\Magento\Store\Model\ResourceModel\Website $subject, $result) + public function afterSave(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website) + { + if ($website->isObjectNew()) { + foreach ($this->getAffectedDimensions($website->getId()) as $dimensions) { + $this->tableMaintainer->createTablesForDimensions($dimensions); + } + } + + return $objectResource; + } + + /** + * Get affected dimensions + * + * @param string $websiteId + * + * @return Dimension[][] + */ + private function getAffectedDimensions(string $websiteId): array { - $this->_processor->markIndexerAsInvalid(); - return $result; + $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration(); + // do not return dimensions if Website dimension is not present in configuration + if (!in_array(WebsiteDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) { + return []; + } + $websiteDimension = $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + $websiteId + ); + + $dimensions = []; + if (in_array(CustomerGroupDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) { + foreach ($this->customerGroupDimensionProvider as $customerGroupDimension) { + $dimensions[] = [ + $customerGroupDimension, + $websiteDimension + ]; + } + } else { + $dimensions[] = [$websiteDimension]; + } + + return $dimensions; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php new file mode 100644 index 0000000000000..eeaa731aac686 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\Indexer\Dimension; +use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; + +/** + * Class return price table name based on dimension + * use only on the frontend area + */ +class PriceTableResolver implements IndexScopeResolverInterface +{ + /** + * @var IndexScopeResolver + */ + private $indexScopeResolver; + + /** + * @var DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @param IndexScopeResolver $indexScopeResolver + * @param DimensionModeConfiguration $dimensionModeConfiguration + */ + public function __construct( + IndexScopeResolver $indexScopeResolver, + DimensionModeConfiguration $dimensionModeConfiguration + ) { + $this->indexScopeResolver = $indexScopeResolver; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + } + + /** + * Return price table name based on dimension + * @param string $index + * @param array $dimensions + * @return string + */ + public function resolve($index, array $dimensions) + { + if ($index === 'catalog_product_index_price') { + $dimensions = $this->filterDimensions($dimensions); + } + return $this->indexScopeResolver->resolve($index, $dimensions); + } + + /** + * @param Dimension[] $dimensions + * @return array + * @throws \Exception + */ + private function filterDimensions($dimensions): array + { + $existDimensions = []; + $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration(); + foreach ($dimensions as $dimension) { + if ((string)$dimension->getValue() === '') { + throw new \InvalidArgumentException( + sprintf('Dimension value of "%s" can not be empty', $dimension->getName()) + ); + } + if (in_array($dimension->getName(), $currentDimensions, true)) { + $existDimensions[] = $dimension; + } + } + + return $existDimensions; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php new file mode 100644 index 0000000000000..e3077baaeb7a6 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php @@ -0,0 +1,280 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Search\Request\Dimension; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; + +/** + * Class encapsulate logic of work with tables per store in Product Price indexer + */ +class TableMaintainer +{ + /** + * Catalog product price index table name + */ + const MAIN_INDEX_TABLE = 'catalog_product_index_price'; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var TableResolver + */ + private $tableResolver; + + /** + * @var AdapterInterface + */ + private $connection; + + /** + * Catalog tmp category index table name + */ + private $tmpTableSuffix = '_temp'; + + /** + * Catalog tmp category index table name + */ + private $additionalTableSuffix = '_replica'; + + /** + * @var string[] + */ + private $mainTmpTable; + + /** + * @var null|string + */ + private $connectionName; + + /** + * @param ResourceConnection $resource + * @param TableResolver $tableResolver + * @param null $connectionName + */ + public function __construct( + ResourceConnection $resource, + TableResolver $tableResolver, + $connectionName = null + ) { + $this->resource = $resource; + $this->tableResolver = $tableResolver; + $this->connectionName = $connectionName; + } + + /** + * Get connection for work with price indexer + * + * @return AdapterInterface + */ + public function getConnection(): AdapterInterface + { + if (null === $this->connection) { + $this->connection = $this->resource->getConnection($this->connectionName); + } + return $this->connection; + } + + /** + * Return validated table name + * + * @param string $table + * @return string + */ + private function getTable(string $table): string + { + return $this->resource->getTableName($table); + } + + /** + * Create table based on main table + * + * @param string $mainTableName + * @param string $newTableName + * + * @return void + * + * @throws \Zend_Db_Exception + */ + private function createTable(string $mainTableName, string $newTableName) + { + if (!$this->getConnection()->isTableExists($newTableName)) { + $this->getConnection()->createTable( + $this->getConnection()->createTableByDdl($mainTableName, $newTableName) + ); + } + } + + /** + * Drop table + * + * @param string $tableName + * + * @return void + */ + private function dropTable(string $tableName) + { + if ($this->getConnection()->isTableExists($tableName)) { + $this->getConnection()->dropTable($tableName); + } + } + + /** + * Truncate table + * + * @param string $tableName + * + * @return void + */ + private function truncateTable(string $tableName) + { + if ($this->getConnection()->isTableExists($tableName)) { + $this->getConnection()->truncateTable($tableName); + } + } + + /** + * Get array key for tmp table + * + * @param Dimension[] $dimensions + * + * @return string + */ + private function getArrayKeyForTmpTable(array $dimensions): string + { + $key = 'temp'; + foreach ($dimensions as $dimension) { + $key .= $dimension->getName() . '_' . $dimension->getValue(); + } + return $key; + } + + /** + * Return main index table name + * + * @param Dimension[] $dimensions + * + * @return string + */ + public function getMainTable(array $dimensions): string + { + return $this->tableResolver->resolve(self::MAIN_INDEX_TABLE, $dimensions); + } + + /** + * Create main and replica index tables for dimensions + * + * @param Dimension[] $dimensions + * + * @return void + * + * @throws \Zend_Db_Exception + */ + public function createTablesForDimensions(array $dimensions) + { + $mainTableName = $this->getMainTable($dimensions); + //Create index table for dimensions based on main replica table + //Using main replica table is necessary for backward capability and TableResolver plugin work + $this->createTable( + $this->getTable(self::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainTableName + ); + + $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + //Create replica table for dimensions based on main replica table + $this->createTable( + $this->getTable(self::MAIN_INDEX_TABLE . $this->additionalTableSuffix), + $mainReplicaTableName + ); + } + + /** + * Drop main and replica index tables for dimensions + * + * @param Dimension[] $dimensions + * + * @return void + */ + public function dropTablesForDimensions(array $dimensions) + { + $mainTableName = $this->getMainTable($dimensions); + $this->dropTable($mainTableName); + + $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + $this->dropTable($mainReplicaTableName); + } + + /** + * Truncate main and replica index tables for dimensions + * + * @param Dimension[] $dimensions + * + * @return void + */ + public function truncateTablesForDimensions(array $dimensions) + { + $mainTableName = $this->getMainTable($dimensions); + $this->truncateTable($mainTableName); + + $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix; + $this->truncateTable($mainReplicaTableName); + } + + /** + * Return replica index table name + * + * @param Dimension[] $dimensions + * + * @return string + */ + public function getMainReplicaTable(array $dimensions): string + { + return $this->getMainTable($dimensions) . $this->additionalTableSuffix; + } + + /** + * Create temporary index table for dimensions + * + * @param Dimension[] $dimensions + * + * @return void + */ + public function createMainTmpTable(array $dimensions) + { + // Create temporary table based on template table catalog_product_index_price_tmp without indexes + $templateTableName = $this->resource->getTableName(self::MAIN_INDEX_TABLE . '_tmp'); + $temporaryTableName = $this->getMainTable($dimensions) . $this->tmpTableSuffix; + $this->getConnection()->createTemporaryTableLike($temporaryTableName, $templateTableName, true); + $this->mainTmpTable[$this->getArrayKeyForTmpTable($dimensions)] = $temporaryTableName; + } + + /** + * Return temporary index table name + * + * @param Dimension[] $dimensions + * + * @return string + * + * @throws \LogicException + */ + public function getMainTmpTable(array $dimensions): string + { + $cacheKey = $this->getArrayKeyForTmpTable($dimensions); + if (!isset($this->mainTmpTable[$cacheKey])) { + throw new \LogicException( + sprintf('Temporary table for provided dimensions "%s" does not exist', $cacheKey) + ); + } + return $this->mainTmpTable[$cacheKey]; + } +} diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php index a4db630f0234b..d21a8666ec0ac 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php @@ -241,7 +241,7 @@ protected function _createItem($label, $value, $count = 0) } /** - * Get all product ids from from collection with applied filters + * Get all product ids from collection with applied filters * * @return array */ diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php index 270a2f229678b..f6d3ca36c1e1e 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php @@ -119,16 +119,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib $attribute->setIsUserDefined($existingModel->getIsUserDefined()); $attribute->setFrontendInput($existingModel->getFrontendInput()); - if (is_array($attribute->getFrontendLabels())) { - $defaultFrontendLabel = $attribute->getDefaultFrontendLabel(); - $frontendLabel[0] = !empty($defaultFrontendLabel) - ? $defaultFrontendLabel - : $existingModel->getDefaultFrontendLabel(); - foreach ($attribute->getFrontendLabels() as $item) { - $frontendLabel[$item->getStoreId()] = $item->getLabel(); - } - $attribute->setDefaultFrontendLabel($frontendLabel); - } + $this->updateDefaultFrontendLabel($attribute, $existingModel); } else { $attribute->setAttributeId(null); @@ -136,22 +127,10 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib throw InputException::requiredField('frontend_label'); } - $frontendLabels = []; - if ($attribute->getDefaultFrontendLabel()) { - $frontendLabels[0] = $attribute->getDefaultFrontendLabel(); - } - if ($attribute->getFrontendLabels() && is_array($attribute->getFrontendLabels())) { - foreach ($attribute->getFrontendLabels() as $label) { - $frontendLabels[$label->getStoreId()] = $label->getLabel(); - } - if (!isset($frontendLabels[0]) || !$frontendLabels[0]) { - throw InputException::invalidFieldValue('frontend_label', null); - } + $frontendLabel = $this->updateDefaultFrontendLabel($attribute, null); - $attribute->setDefaultFrontendLabel($frontendLabels); - } $attribute->setAttributeCode( - $attribute->getAttributeCode() ?: $this->generateCode($frontendLabels[0]) + $attribute->getAttributeCode() ?: $this->generateCode($frontendLabel) ); $this->validateCode($attribute->getAttributeCode()); $this->validateFrontendInput($attribute->getFrontendInput()); @@ -275,4 +254,52 @@ protected function validateFrontendInput($frontendInput) throw InputException::invalidFieldValue('frontend_input', $frontendInput); } } + + /** + * This method sets default frontend value using given default frontend value or frontend value from admin store + * if default frontend value is not presented. + * If both default frontend label and admin store frontend label are not given it throws exception + * for attribute creation process or sets existing attribute value for attribute update action. + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface|null $existingModel + * @return string|null + * @throws InputException + */ + private function updateDefaultFrontendLabel($attribute, $existingModel) + { + $frontendLabel = $attribute->getDefaultFrontendLabel(); + if (empty($frontendLabel)) { + $frontendLabel = $this->extractAdminStoreFrontendLabel($attribute); + if (empty($frontendLabel)) { + if ($existingModel) { + $frontendLabel = $existingModel->getDefaultFrontendLabel(); + } else { + throw InputException::invalidFieldValue('frontend_label', null); + } + } + $attribute->setDefaultFrontendLabel($frontendLabel); + } + return $frontendLabel; + } + + /** + * This method extracts frontend label from FrontendLabel object for admin store. + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @return string|null + */ + private function extractAdminStoreFrontendLabel($attribute) + { + $frontendLabel = []; + $frontendLabels = $attribute->getFrontendLabels(); + if (isset($frontendLabels[0]) + && $frontendLabels[0] instanceof \Magento\Eav\Api\Data\AttributeFrontendLabelInterface + ) { + foreach ($attribute->getFrontendLabels() as $label) { + $frontendLabel[$label->getStoreId()] = $label->getLabel(); + } + } + return $frontendLabel[0] ?? null; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php new file mode 100644 index 0000000000000..68d0877c6cd66 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\Configuration\Item; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\ObjectManager; + +/** + * {@inheritdoc} + */ +class ItemResolverComposite implements ItemResolverInterface +{ + /** @var string[] */ + private $itemResolvers = []; + + /** @var ItemResolverInterface[] */ + private $itemResolversInstances = []; + + /** + * @param string[] $itemResolvers + */ + public function __construct(array $itemResolvers) + { + $this->itemResolvers = $itemResolvers; + } + + /** + * {@inheritdoc} + */ + public function getFinalProduct(ItemInterface $item) : ProductInterface + { + $finalProduct = $item->getProduct(); + foreach ($this->itemResolvers as $resolver) { + $resolvedProduct = $this->getItemResolverInstance($resolver)->getFinalProduct($item); + if ($resolvedProduct !== $finalProduct) { + $finalProduct = $resolvedProduct; + break; + } + } + return $finalProduct; + } + + /** + * Get the instance of the item resolver by class name. + * + * @param string $className + * @return ItemResolverInterface + */ + private function getItemResolverInstance(string $className) : ItemResolverInterface + { + if (!isset($this->itemResolversInstances[$className])) { + $this->itemResolversInstances[$className] = ObjectManager::getInstance()->get($className); + } + return $this->itemResolversInstances[$className]; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php new file mode 100644 index 0000000000000..35c0a7835cb6c --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\Configuration\Item; + +use Magento\Catalog\Api\Data\ProductInterface; + +/** + * Resolves the product from a configured item. + * + * @api + */ +interface ItemResolverInterface +{ + /** + * Get the final product from a configured item by product type and selection. + * + * @param ItemInterface $item + * @return ProductInterface + */ + public function getFinalProduct(ItemInterface $item) : ProductInterface; +} diff --git a/app/code/Magento/Catalog/Model/Product/Option.php b/app/code/Magento/Catalog/Model/Product/Option.php index 39595cdaa60ad..acfc454883e1d 100644 --- a/app/code/Magento/Catalog/Model/Product/Option.php +++ b/app/code/Magento/Catalog/Model/Product/Option.php @@ -8,6 +8,7 @@ use Magento\Catalog\Api\Data\ProductCustomOptionInterface; use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface; +use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterfaceFactory; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product\Option\Value\Collection; @@ -102,6 +103,11 @@ class Option extends AbstractExtensibleModel implements ProductCustomOptionInter */ private $metadataPool; + /** + * @var ProductCustomOptionValuesInterfaceFactory + */ + private $customOptionValuesFactory; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -114,6 +120,7 @@ class Option extends AbstractExtensibleModel implements ProductCustomOptionInter * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param ProductCustomOptionValuesInterfaceFactory|null $customOptionValuesFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -127,12 +134,16 @@ public function __construct( Option\Validator\Pool $validatorPool, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + ProductCustomOptionValuesInterfaceFactory $customOptionValuesFactory = null ) { $this->productOptionValue = $productOptionValue; $this->optionTypeFactory = $optionFactory; $this->validatorPool = $validatorPool; $this->string = $string; + $this->customOptionValuesFactory = $customOptionValuesFactory ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(ProductCustomOptionValuesInterfaceFactory::class); + parent::__construct( $context, $registry, @@ -390,20 +401,21 @@ public function beforeSave() */ public function afterSave() { - $this->getValueInstance()->unsetValues(); $values = $this->getValues() ?: $this->getData('values'); if (is_array($values)) { foreach ($values as $value) { - if ($value instanceof \Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface) { + if ($value instanceof ProductCustomOptionValuesInterface) { $data = $value->getData(); } else { $data = $value; } - $this->getValueInstance()->addValue($data); - } - $this->getValueInstance()->setOption($this)->saveValues(); - } elseif ($this->getGroupByType($this->getType()) == self::OPTION_GROUP_SELECT) { + $this->customOptionValuesFactory->create() + ->addValue($data) + ->setOption($this) + ->saveValues(); + } + } elseif ($this->getGroupByType($this->getType()) === self::OPTION_GROUP_SELECT) { throw new LocalizedException(__('Select type options required values rows.')); } @@ -804,7 +816,7 @@ public function setImageSizeY($imageSizeY) } /** - * @param \Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface[] $values + * @param ProductCustomOptionValuesInterface[] $values * @return $this */ public function setValues(array $values = null) diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php index fd0eae188fea9..9ffe75e513bce 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php @@ -84,7 +84,7 @@ public function validateUserValue($values) */ public function prepareForCart() { - if ($this->getIsValid() && strlen($this->getUserValue()) > 0) { + if ($this->getIsValid() && ($this->getUserValue() !== '')) { return $this->getUserValue(); } else { return null; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php index 1e5c7f76d829b..ee508e30cc93e 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php @@ -106,7 +106,9 @@ protected function isValidOptionTitle($title, $storeId) if ($storeId > \Magento\Store\Model\Store::DEFAULT_STORE_ID && $title === null) { return true; } - if ($this->isEmpty($title)) { + + // checking whether title is null and is empty string + if ($title === null || $title === '') { return false; } @@ -132,7 +134,7 @@ protected function validateOptionType(Option $option) */ protected function validateOptionValue(Option $option) { - return $this->isInRange($option->getPriceType(), $this->priceTypes) && !$this->isNegative($option->getPrice()); + return $this->isInRange($option->getPriceType(), $this->priceTypes); } /** diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php index f04ab497e1d4f..44756890b6ed7 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php @@ -83,7 +83,7 @@ protected function isValidOptionPrice($priceType, $price, $storeId) if (!$priceType && !$price) { return true; } - if (!$this->isInRange($priceType, $this->priceTypes) || $this->isNegative($price)) { + if (!$this->isInRange($priceType, $this->priceTypes)) { return false; } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index fb7759b210bd9..ebbc060c99edf 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -76,6 +76,7 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param CustomOptionPriceCalculator|null $customOptionPriceCalculator */ public function __construct( \Magento\Framework\Model\Context $context, @@ -89,6 +90,7 @@ public function __construct( $this->_valueCollectionFactory = $valueCollectionFactory; $this->customOptionPriceCalculator = $customOptionPriceCalculator ?? \Magento\Framework\App\ObjectManager::getInstance()->get(CustomOptionPriceCalculator::class); + parent::__construct( $context, $registry, @@ -201,19 +203,15 @@ public function getProduct() */ public function saveValues() { + $option = $this->getOption(); + foreach ($this->getValues() as $value) { $this->isDeleted(false); - $this->setData( - $value - )->setData( - 'option_id', - $this->getOption()->getId() - )->setData( - 'store_id', - $this->getOption()->getStoreId() - ); - - if ($this->getData('is_delete') == '1') { + $this->setData($value) + ->setData('option_id', $option->getId()) + ->setData('store_id', $option->getStoreId()); + + if ((bool) $this->getData('is_delete') === true) { if ($this->getId()) { $this->deleteValues($this->getId()); $this->delete(); @@ -222,7 +220,7 @@ public function saveValues() $this->save(); } } - //eof foreach() + return $this; } diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php index d3f0c8be6f649..1b5cf37f6cbb8 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php +++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php @@ -292,13 +292,7 @@ public function attributesCompare($attributeOne, $attributeTwo) $sortOne = $attributeOne->getGroupSortPath() * 1000 + $attributeOne->getSortPath() * 0.0001; $sortTwo = $attributeTwo->getGroupSortPath() * 1000 + $attributeTwo->getSortPath() * 0.0001; - if ($sortOne > $sortTwo) { - return 1; - } elseif ($sortOne < $sortTwo) { - return -1; - } - - return 0; + return $sortOne <=> $sortTwo; } /** diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 4c0122694285d..0dbfda2103471 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -7,9 +7,11 @@ namespace Magento\Catalog\Model; +use Magento\Catalog\Api\Data\ProductExtension; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; @@ -22,6 +24,7 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException; use Magento\Framework\Exception\StateException; use Magento\Framework\Exception\ValidatorException; @@ -306,10 +309,10 @@ protected function getCacheKey($data) * Add product to internal cache and truncate cache if it has more than cacheLimit elements. * * @param string $cacheKey - * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param ProductInterface $product * @return void */ - private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterface $product) + private function cacheProduct($cacheKey, ProductInterface $product) { $this->instancesById[$product->getId()][$cacheKey] = $product; $this->saveProductInLocalCache($product, $cacheKey); @@ -326,7 +329,7 @@ private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterf * * @param array $productData * @param bool $createNew - * @return \Magento\Catalog\Api\Data\ProductInterface|Product + * @return ProductInterface|Product * @throws NoSuchEntityException */ protected function initializeProductData(array $productData, $createNew) @@ -414,12 +417,12 @@ protected function processNewMediaGalleryEntry( /** * Process product links, creating new links, updating and deleting existing links * - * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param ProductInterface $product * @param \Magento\Catalog\Api\Data\ProductLinkInterface[] $newLinks * @return $this * @throws NoSuchEntityException */ - private function processLinks(\Magento\Catalog\Api\Data\ProductInterface $product, $newLinks) + private function processLinks(ProductInterface $product, $newLinks) { if ($newLinks === null) { // If product links were not specified, don't do anything @@ -514,13 +517,14 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE $newEntries = $mediaGalleryEntries; } + $images = (array)$product->getMediaGallery('images'); + $images = $this->determineImageRoles($product, $images); + $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); - $images = $product->getMediaGallery('images'); - if ($images) { - foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { - $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); - } + + foreach ($images as $image) { + if (!isset($image['removed']) && !empty($image['types'])) { + $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); } } @@ -547,11 +551,11 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveOptions = false) + public function save(ProductInterface $product, $saveOptions = false) { $tierPrices = $product->getData('tier_price'); @@ -565,12 +569,18 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO if (!$product->hasData(Product::STATUS)) { $product->setStatus($existingProduct->getStatus()); } + + /** @var ProductExtension $extensionAttributes */ + $extensionAttributes = $product->getExtensionAttributes(); + if (empty($extensionAttributes->__toArray())) { + $product->setExtensionAttributes($existingProduct->getExtensionAttributes()); + } } catch (NoSuchEntityException $e) { $existingProduct = null; } $productDataArray = $this->extensibleDataObjectConverter - ->toNestedArray($product, [], \Magento\Catalog\Api\Data\ProductInterface::class); + ->toNestedArray($product, [], ProductInterface::class); $productDataArray = array_replace($productDataArray, $product->getData()); $ignoreLinksFlag = $product->getData('ignore_links_flag'); $productLinks = null; @@ -596,47 +606,11 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO ); } - try { - if ($tierPrices !== null) { - $product->setData('tier_price', $tierPrices); - } - $this->removeProductFromLocalCache($product->getSku()); - unset($this->instancesById[$product->getId()]); - $this->resourceModel->save($product); - } catch (ConnectionException $exception) { - throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( - __('Database connection error'), - $exception, - $exception->getCode() - ); - } catch (DeadlockException $exception) { - throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( - __('Database deadlock found when trying to get lock'), - $exception, - $exception->getCode() - ); - } catch (LockWaitException $exception) { - throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException( - __('Database lock wait timeout exceeded'), - $exception, - $exception->getCode() - ); - } catch (\Magento\Eav\Model\Entity\Attribute\Exception $exception) { - throw \Magento\Framework\Exception\InputException::invalidFieldValue( - $exception->getAttributeCode(), - $product->getData($exception->getAttributeCode()), - $exception - ); - } catch (ValidatorException $e) { - throw new CouldNotSaveException(__($e->getMessage())); - } catch (LocalizedException $e) { - throw $e; - } catch (\Exception $e) { - throw new \Magento\Framework\Exception\CouldNotSaveException( - __('The product was unable to be saved. Please try again.'), - $e - ); + if ($tierPrices !== null) { + $product->setData('tier_price', $tierPrices); } + + $this->saveProduct($product); $this->removeProductFromLocalCache($product->getSku()); unset($this->instancesById[$product->getId()]); @@ -646,7 +620,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO /** * {@inheritdoc} */ - public function delete(\Magento\Catalog\Api\Data\ProductInterface $product) + public function delete(ProductInterface $product) { $sku = $product->getSku(); $productId = $product->getId(); @@ -758,6 +732,32 @@ public function cleanCache() $this->instancesById = null; } + /** + * Ascertain image roles, if they are not set against the gallery entries + * + * @param ProductInterface $product + * @param array $images + * @return array + */ + private function determineImageRoles(ProductInterface $product, array $images) : array + { + $imagesWithRoles = []; + foreach ($images as $image) { + if (!isset($image['types'])) { + $image['types'] = []; + if (isset($image['file'])) { + foreach (array_keys($product->getMediaAttributes()) as $attribute) { + if ($image['file'] == $product->getData($attribute)) { + $image['types'][] = $attribute; + } + } + } + } + $imagesWithRoles[] = $image; + } + return $imagesWithRoles; + } + /** * @return Product\Gallery\Processor */ @@ -835,4 +835,55 @@ private function prepareSku(string $sku): string { return mb_strtolower(trim($sku)); } + + /** + * Save product resource model. + * + * @param ProductInterface|Product $product + * @throws TemporaryCouldNotSaveException + * @throws InputException + * @throws CouldNotSaveException + * @throws LocalizedException + */ + private function saveProduct($product): void + { + try { + $this->removeProductFromLocalCache($product->getSku()); + unset($this->instancesById[$product->getId()]); + $this->resourceModel->save($product); + } catch (ConnectionException $exception) { + throw new TemporaryCouldNotSaveException( + __('Database connection error'), + $exception, + $exception->getCode() + ); + } catch (DeadlockException $exception) { + throw new TemporaryCouldNotSaveException( + __('Database deadlock found when trying to get lock'), + $exception, + $exception->getCode() + ); + } catch (LockWaitException $exception) { + throw new TemporaryCouldNotSaveException( + __('Database lock wait timeout exceeded'), + $exception, + $exception->getCode() + ); + } catch (AttributeException $exception) { + throw InputException::invalidFieldValue( + $exception->getAttributeCode(), + $product->getData($exception->getAttributeCode()), + $exception + ); + } catch (ValidatorException $e) { + throw new CouldNotSaveException(__($e->getMessage())); + } catch (LocalizedException $e) { + throw $e; + } catch (\Exception $e) { + throw new CouldNotSaveException( + __('The product was unable to be saved. Please try again.'), + $e + ); + } + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index fa68ae3f865ef..9de0e8a849046 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -490,7 +490,7 @@ public function getProductsPosition($category) } /** - * Get chlden categories count + * Get children categories count * * @param int $categoryId * @return int diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php index bed129e19168f..3699e29f8201a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php @@ -5,6 +5,15 @@ */ namespace Magento\Catalog\Model\ResourceModel\Layer\Filter; +use Magento\Framework\App\Http\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Customer\Model\Context as CustomerContext; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; + /** * Catalog Layer Price Filter resource model * @@ -41,6 +50,21 @@ class Price extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ private $storeManager; + /** + * @var IndexScopeResolverInterface|null + */ + private $priceTableResolver; + + /** + * @var Context + */ + private $httpContext; + + /** + * @var DimensionFactory|null + */ + private $dimensionFactory; + /** * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\Event\ManagerInterface $eventManager @@ -48,6 +72,9 @@ class Price extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb * @param \Magento\Customer\Model\Session $session * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param null $connectionName + * @param IndexScopeResolverInterface|null $priceTableResolver + * @param Context|null $httpContext + * @param DimensionFactory|null $dimensionFactory */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -55,12 +82,19 @@ public function __construct( \Magento\Catalog\Model\Layer\Resolver $layerResolver, \Magento\Customer\Model\Session $session, \Magento\Store\Model\StoreManagerInterface $storeManager, - $connectionName = null + $connectionName = null, + IndexScopeResolverInterface $priceTableResolver = null, + Context $httpContext = null, + DimensionFactory $dimensionFactory = null ) { $this->layer = $layerResolver->get(); $this->session = $session; $this->storeManager = $storeManager; $this->_eventManager = $eventManager; + $this->priceTableResolver = $priceTableResolver + ?? ObjectManager::getInstance()->get(IndexScopeResolverInterface::class); + $this->httpContext = $httpContext ?? ObjectManager::getInstance()->get(Context::class); + $this->dimensionFactory = $dimensionFactory ?? ObjectManager::getInstance()->get(DimensionFactory::class); parent::__construct($context, $connectionName); } @@ -118,11 +152,8 @@ protected function _getSelect() // remove join with main table $fromPart = $select->getPart(\Magento\Framework\DB\Select::FROM); - if (!isset( - $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS] - ) || !isset( - $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS] - ) + if (!isset($fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS]) || + !isset($fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS]) ) { return $select; } @@ -376,6 +407,30 @@ protected function _construct() $this->_init('catalog_product_index_price', 'entity_id'); } + /** + * {@inheritdoc} + * @return string + */ + public function getMainTable() + { + $storeKey = $this->httpContext->getValue(StoreManagerInterface::CONTEXT_STORE); + $priceTableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$this->storeManager->getStore($storeKey)->getWebsiteId() + ), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$this->httpContext->getValue(CustomerContext::CONTEXT_GROUP) + ) + ] + ); + + return $this->getTable($priceTableName); + } + /** * Retrieve joined price index table alias * diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 9b87515450a12..4384effc4e359 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -12,11 +12,15 @@ use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\Customer\Api\GroupManagementInterface; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Framework\Indexer\DimensionFactory; /** * Product collection @@ -276,9 +280,18 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $tableMaintainer; + /** + * @var PriceTableResolver + */ + private $priceTableResolver; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + /** * Collection constructor - * * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy @@ -302,7 +315,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool * @param TableMaintainer|null $tableMaintainer - * + * @param PriceTableResolver|null $priceTableResolver + * @param DimensionFactory|null $dimensionFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -328,7 +342,9 @@ public function __construct( \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, ProductLimitationFactory $productLimitationFactory = null, MetadataPool $metadataPool = null, - TableMaintainer $tableMaintainer = null + TableMaintainer $tableMaintainer = null, + PriceTableResolver $priceTableResolver = null, + DimensionFactory $dimensionFactory = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -359,6 +375,9 @@ public function __construct( $connection ); $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); + $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); + $this->dimensionFactory = $dimensionFactory + ?: ObjectManager::getInstance()->get(DimensionFactory::class); } /** @@ -913,7 +932,7 @@ private function mapConditionType($conditionType) 'eq' => 'in', 'neq' => 'nin' ]; - return isset($conditionsMap[$conditionType]) ? $conditionsMap[$conditionType] : $conditionType; + return $conditionsMap[$conditionType] ?? $conditionType; } /** @@ -1061,14 +1080,15 @@ public function getAllAttributeValues($attribute) $select = clone $this->getSelect(); $attribute = $this->getEntity()->getAttribute($attribute); - $aiField = $this->getConnection()->getAutoIncrementField($this->getMainTable()); + $fieldMainTable = $this->getConnection()->getAutoIncrementField($this->getMainTable()); + $fieldJoinTable = $attribute->getEntity()->getLinkField(); $select->reset() ->from( ['cpe' => $this->getMainTable()], ['entity_id'] )->join( ['cpa' => $attribute->getBackend()->getTable()], - 'cpe.' . $aiField . ' = cpa.' . $aiField, + 'cpe.' . $fieldMainTable . ' = cpa.' . $fieldJoinTable, ['store_id', 'value'] )->where('attribute_id = ?', (int)$attribute->getId()); @@ -1680,7 +1700,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) return $this; } elseif ($attribute == 'is_saleable') { - $this->getSelect()->order("is_saleable " . $dir); + $this->getSelect()->order("is_salable " . $dir); return $this; } @@ -1863,7 +1883,12 @@ protected function _productLimitationJoinPrice() protected function _productLimitationPrice($joinLeft = false) { $filters = $this->_productLimitationFilters; - if (!$filters->isUsingPriceIndex()) { + if (!$filters->isUsingPriceIndex() || + !isset($filters['website_id']) || + (string)$filters['website_id'] === '' || + !isset($filters['customer_group_id']) || + (string)$filters['customer_group_id'] === '' + ) { return $this; } @@ -1898,7 +1923,23 @@ protected function _productLimitationPrice($joinLeft = false) 'max_price', 'tier_price', ]; - $tableName = ['price_index' => $this->getTable('catalog_product_index_price')]; + + $tableName = [ + 'price_index' => $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$filters['customer_group_id'] + ), + $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$filters['website_id'] + ) + ] + ) + ]; + if ($joinLeft) { $select->joinLeft($tableName, $joinCond, $colls); } else { @@ -2228,6 +2269,7 @@ public function addPriceDataFieldFilter($comparisonFormat, $fields) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @since 101.0.1 + * @throws \Magento\Framework\Exception\LocalizedException */ public function addMediaGalleryData() { @@ -2239,34 +2281,36 @@ public function addMediaGalleryData() return $this; } - /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $attribute = $this->getAttribute('media_gallery'); - $select = $this->getMediaGalleryResource()->createBatchBaseSelect( - $this->getStoreId(), - $attribute->getAttributeId() - ); - - $mediaGalleries = []; - $linkField = $this->getProductEntityMetadata()->getLinkField(); $items = $this->getItems(); + $linkField = $this->getProductEntityMetadata()->getLinkField(); - $select->where( - 'entity.' . $linkField . ' IN (?)', - array_map( - function ($item) use ($linkField) { - return $item->getData($linkField); - }, - $items - ) - ); + $select = $this->getMediaGalleryResource() + ->createBatchBaseSelect( + $this->getStoreId(), + $this->getAttribute('media_gallery')->getAttributeId() + )->reset( + Select::ORDER // we don't care what order is in current scenario + )->where( + 'entity.' . $linkField . ' IN (?)', + array_map( + function ($item) use ($linkField) { + return (int) $item->getOrigData($linkField); + }, + $items + ) + ); + + $mediaGalleries = []; foreach ($this->getConnection()->fetchAll($select) as $row) { $mediaGalleries[$row[$linkField]][] = $row; } foreach ($items as $item) { - $mediaEntries = isset($mediaGalleries[$item->getData($linkField)]) ? - $mediaGalleries[$item->getData($linkField)] : []; - $this->getGalleryReadHandler()->addMediaDataToProduct($item, $mediaEntries); + $this->getGalleryReadHandler() + ->addMediaDataToProduct( + $item, + $mediaGalleries[$item->getOrigData($linkField)] ?? [] + ); } $this->setFlag('media_gallery_added', true); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index ee1df8f23424d..ebe04fb63b217 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -7,9 +7,13 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuilderInterface { @@ -38,6 +42,16 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild */ private $baseSelectProcessor; + /** + * @var IndexScopeResolverInterface|null + */ + private $priceTableResolver; + + /** + * @var DimensionFactory|null + */ + private $dimensionFactory; + /** * LinkedProductSelectBuilderByIndexPrice constructor. * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -45,13 +59,17 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param BaseSelectProcessorInterface|null $baseSelectProcessor + * @param IndexScopeResolverInterface|null $priceTableResolver + * @param DimensionFactory|null $dimensionFactory */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Customer\Model\Session $customerSession, \Magento\Framework\EntityManager\MetadataPool $metadataPool, - BaseSelectProcessorInterface $baseSelectProcessor = null + BaseSelectProcessorInterface $baseSelectProcessor = null, + IndexScopeResolverInterface $priceTableResolver = null, + DimensionFactory $dimensionFactory = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; @@ -59,6 +77,9 @@ public function __construct( $this->metadataPool = $metadataPool; $this->baseSelectProcessor = (null !== $baseSelectProcessor) ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); + $this->priceTableResolver = $priceTableResolver + ?? ObjectManager::getInstance()->get(IndexScopeResolverInterface::class); + $this->dimensionFactory = $dimensionFactory ?? ObjectManager::getInstance()->get(DimensionFactory::class); } /** @@ -68,6 +89,8 @@ public function build($productId) { $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); + $websiteId = $this->storeManager->getStore()->getWebsiteId(); + $customerGroupId = $this->customerSession->getCustomerGroupId(); $priceSelect = $this->resource->getConnection()->select() ->from(['parent' => $productTable], '') @@ -80,12 +103,20 @@ public function build($productId) sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( - ['t' => $this->resource->getTableName('catalog_product_index_price')], + [ + 't' => $this->priceTableResolver->resolve('catalog_product_index_price', [ + $this->dimensionFactory->create(WebsiteDimensionProvider::DIMENSION_NAME, (string)$websiteId), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$customerGroupId + ), + ]) + ], sprintf('t.entity_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] )->where('parent.entity_id = ?', $productId) - ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) - ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) + ->where('t.website_id = ?', $websiteId) + ->where('t.customer_group_id = ?', $customerGroupId) ->order('t.min_price ' . Select::SQL_ASC) ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php new file mode 100644 index 0000000000000..cf5ba451c380e --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price; + +/** + * Apply price modifiers to product price indexer which are common for all product types: + * custom options, catalog rule, catalog inventory modifiers + */ +class BasePriceModifier implements PriceModifierInterface +{ + /** + * @var PriceModifierInterface[] + */ + private $priceModifiers; + + /** + * @param array $priceModifiers + */ + public function __construct(array $priceModifiers) + { + $this->priceModifiers = $priceModifiers; + } + + /** + * {@inheritdoc} + */ + public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void + { + foreach ($this->priceModifiers as $priceModifier) { + $priceModifier->modifyPrice($priceTable, $entityIds); + } + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php index 24cb4fedd57e5..a499777df871a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php @@ -18,7 +18,7 @@ class CompositeProductRowSizeEstimator implements IndexTableRowSizeEstimatorInte /** * Calculated memory size for one record in catalog_product_index_price table */ - const MEMORY_SIZE_FOR_ONE_ROW = 250; + const MEMORY_SIZE_FOR_ONE_ROW = 200; /** * @var WebsiteManagementInterface diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php new file mode 100644 index 0000000000000..47fc6802d7eaf --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php @@ -0,0 +1,464 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\DB\Sql\ColumnValueExpression; + +/** + * Class for modify custom option price. + */ +class CustomOptionPriceModifier implements PriceModifierInterface +{ + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + + /** + * @var \Magento\Framework\DB\Sql\ColumnValueExpression + */ + private $columnValueExpressionFactory; + + /** + * @var \Magento\Catalog\Helper\Data + */ + private $dataHelper; + + /** + * @var string + */ + private $connectionName; + + /** + * @var bool + */ + private $isPriceGlobalFlag; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var \Magento\Framework\Indexer\Table\StrategyInterface + */ + private $tableStrategy; + + /** + * @param \Magento\Framework\App\ResourceConnection $resource + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param \Magento\Framework\DB\Sql\ColumnValueExpressionFactory $columnValueExpressionFactory + * @param \Magento\Catalog\Helper\Data $dataHelper + * @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy + * @param string $connectionName + */ + public function __construct( + \Magento\Framework\App\ResourceConnection $resource, + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + \Magento\Framework\DB\Sql\ColumnValueExpressionFactory $columnValueExpressionFactory, + \Magento\Catalog\Helper\Data $dataHelper, + \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy, + $connectionName = 'indexer' + ) { + $this->resource = $resource; + $this->metadataPool = $metadataPool; + $this->connectionName = $connectionName; + $this->columnValueExpressionFactory = $columnValueExpressionFactory; + $this->dataHelper = $dataHelper; + $this->tableStrategy = $tableStrategy; + } + + /** + * Apply custom option price to temporary index price table + * + * @param IndexTableStructure $priceTable + * @param array $entityIds + * @return void + * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void + { + // no need to run all queries if current products have no custom options + if (!$this->checkIfCustomOptionsExist($priceTable)) { + return; + } + + $connection = $this->getConnection(); + $finalPriceTable = $priceTable->getTableName(); + + $coaTable = $this->getCustomOptionAggregateTable(); + $this->prepareCustomOptionAggregateTable(); + + $copTable = $this->getCustomOptionPriceTable(); + $this->prepareCustomOptionPriceTable(); + + $select = $this->getSelectForOptionsWithMultipleValues($finalPriceTable); + $query = $select->insertFromSelect($coaTable); + $connection->query($query); + + $select = $this->getSelectForOptionsWithOneValue($finalPriceTable); + $query = $select->insertFromSelect($coaTable); + $connection->query($query); + + $select = $this->getSelectAggregated($coaTable); + $query = $select->insertFromSelect($copTable); + $connection->query($query); + + // update tmp price index with prices from custom options (from previous aggregated table) + $select = $this->getSelectForUpdate($copTable); + $query = $select->crossUpdateFromSelect(['i' => $finalPriceTable]); + $connection->query($query); + + $connection->delete($coaTable); + $connection->delete($copTable); + } + + /** + * @param IndexTableStructure $priceTable + * @return bool + * @throws \Exception + */ + private function checkIfCustomOptionsExist(IndexTableStructure $priceTable): bool + { + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + + $select = $this->getConnection() + ->select() + ->from( + ['i' => $priceTable->getTableName()], + ['entity_id'] + )->join( + ['e' => $this->getTable('catalog_product_entity')], + 'e.entity_id = i.entity_id', + [] + )->join( + ['o' => $this->getTable('catalog_product_option')], + 'o.product_id = e.' . $metadata->getLinkField(), + ['option_id'] + ); + + return !empty($this->getConnection()->fetchRow($select)); + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + if (null === $this->connection) { + $this->connection = $this->resource->getConnection($this->connectionName); + } + + return $this->connection; + } + + /** + * Prepare prices for products with custom options that has multiple values + * + * @param string $sourceTable + * @return \Magento\Framework\DB\Select + * @throws \Exception + */ + private function getSelectForOptionsWithMultipleValues(string $sourceTable): Select + { + $connection = $this->resource->getConnection($this->connectionName); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + + $select = $connection->select() + ->from( + ['i' => $sourceTable], + ['entity_id', 'customer_group_id', 'website_id'] + )->join( + ['e' => $this->getTable('catalog_product_entity')], + 'e.entity_id = i.entity_id', + [] + )->join( + ['cwd' => $this->getTable('catalog_product_index_website')], + 'i.website_id = cwd.website_id', + [] + )->join( + ['o' => $this->getTable('catalog_product_option')], + 'o.product_id = e.' . $metadata->getLinkField(), + ['option_id'] + )->join( + ['ot' => $this->getTable('catalog_product_option_type_value')], + 'ot.option_id = o.option_id', + [] + )->join( + ['otpd' => $this->getTable('catalog_product_option_type_price')], + 'otpd.option_type_id = ot.option_type_id AND otpd.store_id = 0', + [] + )->group( + ['i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id'] + ); + + if ($this->isPriceGlobal()) { + $optPriceType = 'otpd.price_type'; + $optPriceValue = 'otpd.price'; + } else { + $select->joinLeft( + ['otps' => $this->getTable('catalog_product_option_type_price')], + 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cwd.default_store_id', + [] + ); + + $optPriceType = $connection->getCheckSql( + 'otps.option_type_price_id > 0', + 'otps.price_type', + 'otpd.price_type' + ); + $optPriceValue = $connection->getCheckSql('otps.option_type_price_id > 0', 'otps.price', 'otpd.price'); + } + + $minPriceRound = $this->columnValueExpressionFactory + ->create([ + 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)" + ]); + $minPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound); + $minPriceMin = $this->columnValueExpressionFactory + ->create([ + 'expression' => "MIN({$minPriceExpr})" + ]); + $minPrice = $connection->getCheckSql("MIN(o.is_require) = 1", $minPriceMin, '0'); + + $tierPriceRound = $this->columnValueExpressionFactory + ->create([ + 'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)" + ]); + $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound); + $tierPriceMin = $this->columnValueExpressionFactory + ->create([ + 'expression' => "MIN({$tierPriceExpr})" + ]); + $tierPriceValue = $connection->getCheckSql("MIN(o.is_require) > 0", $tierPriceMin, 0); + $tierPrice = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", $tierPriceValue, "NULL"); + + $maxPriceRound = $this->columnValueExpressionFactory + ->create([ + 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)" + ]); + $maxPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $maxPriceRound); + $maxPrice = $connection->getCheckSql( + "(MIN(o.type)='radio' OR MIN(o.type)='drop_down')", + "MAX({$maxPriceExpr})", + "SUM({$maxPriceExpr})" + ); + + $select->columns( + [ + 'min_price' => $minPrice, + 'max_price' => $maxPrice, + 'tier_price' => $tierPrice, + ] + ); + + return $select; + } + + /** + * Prepare prices for products with custom options that has single value + * + * @param string $sourceTable + * @return \Magento\Framework\DB\Select + * @throws \Exception + */ + private function getSelectForOptionsWithOneValue(string $sourceTable): Select + { + $connection = $this->resource->getConnection($this->connectionName); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + + $select = $connection->select() + ->from( + ['i' => $sourceTable], + ['entity_id', 'customer_group_id', 'website_id'] + )->join( + ['e' => $this->getTable('catalog_product_entity')], + 'e.entity_id = i.entity_id', + [] + )->join( + ['cwd' => $this->getTable('catalog_product_index_website')], + 'i.website_id = cwd.website_id', + [] + )->join( + ['o' => $this->getTable('catalog_product_option')], + 'o.product_id = e.' . $metadata->getLinkField(), + ['option_id'] + )->join( + ['opd' => $this->getTable('catalog_product_option_price')], + 'opd.option_id = o.option_id AND opd.store_id = 0', + [] + ); + + if ($this->isPriceGlobal()) { + $optPriceType = 'opd.price_type'; + $optPriceValue = 'opd.price'; + } else { + $select->joinLeft( + ['ops' => $this->getTable('catalog_product_option_price')], + 'ops.option_id = opd.option_id AND ops.store_id = cwd.default_store_id', + [] + ); + + $optPriceType = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price_type', 'opd.price_type'); + $optPriceValue = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price', 'opd.price'); + } + + $minPriceRound = $this->columnValueExpressionFactory + ->create([ + 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)" + ]); + $priceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound); + $minPrice = $connection->getCheckSql("{$priceExpr} > 0 AND o.is_require = 1", $priceExpr, 0); + + $maxPrice = $priceExpr; + + $tierPriceRound = $this->columnValueExpressionFactory + ->create([ + 'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)" + ]); + $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound); + $tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require = 1", $tierPriceExpr, 0); + $tierPrice = $connection->getCheckSql("i.tier_price IS NOT NULL", $tierPriceValue, "NULL"); + + $select->columns( + [ + 'min_price' => $minPrice, + 'max_price' => $maxPrice, + 'tier_price' => $tierPrice, + ] + ); + + return $select; + } + + /** + * Aggregate prices with one and multiply options into one table + * + * @param string $sourceTable + * @return \Magento\Framework\DB\Select + */ + private function getSelectAggregated(string $sourceTable): Select + { + $connection = $this->resource->getConnection($this->connectionName); + + $select = $connection->select() + ->from( + [$sourceTable], + [ + 'entity_id', + 'customer_group_id', + 'website_id', + 'min_price' => 'SUM(min_price)', + 'max_price' => 'SUM(max_price)', + 'tier_price' => 'SUM(tier_price)', + ] + )->group( + ['entity_id', 'customer_group_id', 'website_id'] + ); + + return $select; + } + + /** + * @param string $sourceTable + * @return \Magento\Framework\DB\Select + */ + private function getSelectForUpdate(string $sourceTable): Select + { + $connection = $this->resource->getConnection($this->connectionName); + + $select = $connection->select()->join( + ['io' => $sourceTable], + 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' . + ' AND i.website_id = io.website_id', + [] + ); + $select->columns( + [ + 'min_price' => new ColumnValueExpression('i.min_price + io.min_price'), + 'max_price' => new ColumnValueExpression('i.max_price + io.max_price'), + 'tier_price' => $connection->getCheckSql( + 'i.tier_price IS NOT NULL', + 'i.tier_price + io.tier_price', + 'NULL' + ), + ] + ); + + return $select; + } + + /** + * @param string $tableName + * @return string + */ + private function getTable(string $tableName): string + { + return $this->resource->getTableName($tableName, $this->connectionName); + } + + /** + * @return bool + */ + private function isPriceGlobal(): bool + { + if ($this->isPriceGlobalFlag === null) { + $this->isPriceGlobalFlag = $this->dataHelper->isPriceGlobal(); + } + + return $this->isPriceGlobalFlag; + } + + /** + * Retrieve table name for custom option temporary aggregation data + * + * @return string + */ + private function getCustomOptionAggregateTable(): string + { + return $this->tableStrategy->getTableName('catalog_product_index_price_opt_agr'); + } + + /** + * Retrieve table name for custom option prices data + * + * @return string + */ + private function getCustomOptionPriceTable(): string + { + return $this->tableStrategy->getTableName('catalog_product_index_price_opt'); + } + + /** + * Prepare table structure for custom option temporary aggregation data + * + * @return void + */ + private function prepareCustomOptionAggregateTable() + { + $this->getConnection()->delete($this->getCustomOptionAggregateTable()); + } + + /** + * Prepare table structure for custom option prices data + * + * @return void + */ + private function prepareCustomOptionPriceTable() + { + $this->getConnection()->delete($this->getCustomOptionPriceTable()); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 4ca407a53f8ae..168fa8f50acc2 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price; use Magento\Catalog\Model\ResourceModel\Product\Indexer\AbstractIndexer; +use Magento\Framework\Indexer\DimensionalIndexerInterface; /** * Default Product Type Price Indexer Resource model @@ -16,6 +17,8 @@ * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 + * @deprecated Not used anymore for price indexation. Class left for backward compatibility + * @see DimensionalIndexerInterface */ class DefaultPrice extends AbstractIndexer implements PriceInterface { @@ -71,7 +74,7 @@ class DefaultPrice extends AbstractIndexer implements PriceInterface * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\Module\Manager $moduleManager * @param string|null $connectionName - * @param null|IndexTableStructureFactory $indexTableStructureFactory + * @param IndexTableStructureFactory $indexTableStructureFactory * @param PriceModifierInterface[] $priceModifiers */ public function __construct( @@ -307,7 +310,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type) $query = $select->insertFromSelect($finalPriceTable->getTableName(), [], false); $this->getConnection()->query($query); - $this->applyDiscountPrices($finalPriceTable); + $this->modifyPriceIndex($finalPriceTable); return $this; } @@ -327,6 +330,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type) protected function getSelect($entityIds = null, $type = null) { $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $linkField = $metadata->getLinkField(); $connection = $this->getConnection(); $select = $connection->select()->from( ['e' => $this->getTable('catalog_product_entity')], @@ -356,9 +360,38 @@ protected function getSelect($entityIds = null, $type = null) 'pw.product_id = e.entity_id AND pw.website_id = cw.website_id', [] )->joinLeft( - ['tp' => $this->_getTierPriceIndexTable()], - 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' . - ' AND tp.customer_group_id = cg.customer_group_id', + // we need this only for BCC in case someone expects table `tp` to be present in query + ['tp' => $this->getTable('catalog_product_index_tier_price')], + 'tp.entity_id = e.entity_id AND tp.customer_group_id = cg.customer_group_id' . + ' AND tp.website_id = pw.website_id', + [] + )->joinLeft( + // calculate tier price specified as Website = `All Websites` and Customer Group = `Specific Customer Group` + ['tier_price_1' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_1.' . $linkField . ' = e.' . $linkField . ' AND tier_price_1.all_groups = 0' . + ' AND tier_price_1.customer_group_id = cg.customer_group_id AND tier_price_1.qty = 1' . + ' AND tier_price_1.website_id = 0', + [] + )->joinLeft( + // calculate tier price specified as Website = `Specific Website` + //and Customer Group = `Specific Customer Group` + ['tier_price_2' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_2.' . $linkField . ' = e.' . $linkField . ' AND tier_price_2.all_groups = 0' . + ' AND tier_price_2.customer_group_id = cg.customer_group_id AND tier_price_2.qty = 1' . + ' AND tier_price_2.website_id = cw.website_id', + [] + )->joinLeft( + // calculate tier price specified as Website = `All Websites` and Customer Group = `ALL GROUPS` + ['tier_price_3' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_3.' . $linkField . ' = e.' . $linkField . ' AND tier_price_3.all_groups = 1' . + ' AND tier_price_3.customer_group_id = 0 AND tier_price_3.qty = 1 AND tier_price_3.website_id = 0', + [] + )->joinLeft( + // calculate tier price specified as Website = `Specific Website` and Customer Group = `ALL GROUPS` + ['tier_price_4' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_4.' . $linkField . ' = e.' . $linkField . ' AND tier_price_4.all_groups = 1' . + ' AND tier_price_4.customer_group_id = 0 AND tier_price_4.qty = 1' . + ' AND tier_price_4.website_id = cw.website_id', [] ); @@ -374,7 +407,7 @@ protected function getSelect($entityIds = null, $type = null) $this->_addAttributeToSelect( $select, 'status', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id', $statusCond, true @@ -383,7 +416,7 @@ protected function getSelect($entityIds = null, $type = null) $taxClassId = $this->_addAttributeToSelect( $select, 'tax_class_id', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id' ); } else { @@ -394,25 +427,25 @@ protected function getSelect($entityIds = null, $type = null) $price = $this->_addAttributeToSelect( $select, 'price', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id' ); $specialPrice = $this->_addAttributeToSelect( $select, 'special_price', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id' ); $specialFrom = $this->_addAttributeToSelect( $select, 'special_from_date', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id' ); $specialTo = $this->_addAttributeToSelect( $select, 'special_to_date', - 'e.' . $metadata->getLinkField(), + 'e.' . $linkField, 'cs.store_id' ); $currentDate = 'cwd.website_date'; @@ -423,15 +456,12 @@ protected function getSelect($entityIds = null, $type = null) $specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}"; $specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}"; $specialPriceExpr = $connection->getCheckSql( - "{$specialPrice} IS NOT NULL AND {$specialFromExpr} AND {$specialToExpr}", + "{$specialPrice} IS NOT NULL AND ({$specialFromExpr}) AND ({$specialToExpr})", $specialPrice, $maxUnsignedBigint ); - $tierPrice = new \Zend_Db_Expr('tp.min_price'); - $tierPriceExpr = $connection->getIfNullSql( - $tierPrice, - $maxUnsignedBigint - ); + $tierPrice = $this->getTotalTierPriceExpression($price); + $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint); $finalPrice = $connection->getLeastSql([ $price, $specialPriceExpr, @@ -512,12 +542,12 @@ protected function _prepareCustomOptionPriceTable() } /** - * Apply discount prices to final price index table. + * Modify data in price index table. * * @param IndexTableStructure $finalPriceTable * @return void */ - private function applyDiscountPrices(IndexTableStructure $finalPriceTable) : void + private function modifyPriceIndex(IndexTableStructure $finalPriceTable) : void { foreach ($this->priceModifiers as $priceModifier) { $priceModifier->modifyPrice($finalPriceTable); @@ -791,4 +821,62 @@ protected function hasEntity() return $this->hasEntity; } + + /** + * @param \Zend_Db_Expr $priceExpression + * @return \Zend_Db_Expr + */ + private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) + { + $maxUnsignedBigint = '~0'; + + return $this->getConnection()->getCheckSql( + implode( + ' AND ', + [ + 'tier_price_1.value_id is NULL', + 'tier_price_2.value_id is NULL', + 'tier_price_3.value_id is NULL', + 'tier_price_4.value_id is NULL' + ] + ), + 'NULL', + $this->getConnection()->getLeastSql([ + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_2', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_3', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression), + $maxUnsignedBigint + ), + ]) + ); + } + + /** + * @param string $tableAlias + * @param \Zend_Db_Expr $priceExpression + * @return \Zend_Db_Expr + */ + private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $priceExpression) + { + return $this->getConnection()->getCheckSql( + sprintf('%s.value = 0', $tableAlias), + sprintf( + 'ROUND(%s * (1 - ROUND(%s.percentage_value * cwd.rate, 4) / 100), 4)', + $priceExpression, + $tableAlias + ), + sprintf('ROUND(%s.value * cwd.rate, 4)', $tableAlias) + ); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php index 21a7647214c26..9a310c7365ac9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php @@ -9,6 +9,8 @@ */ namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price; +use Magento\Framework\Indexer\DimensionalIndexerInterface; + class Factory { /** @@ -40,14 +42,17 @@ public function create($className, array $data = []) { $indexerPrice = $this->_objectManager->create($className, $data); - if (!$indexerPrice instanceof \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice) { - throw new \Magento\Framework\Exception\LocalizedException( - __( - '%1 doesn\'t extend \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice', - $className - ) - ); + if ($indexerPrice instanceof PriceInterface || $indexerPrice instanceof DimensionalIndexerInterface) { + return $indexerPrice; } - return $indexerPrice; + + throw new \Magento\Framework\Exception\LocalizedException( + __( + 'Price indexer "%1" must implement %2 or %3', + $className, + PriceInterface::class, + DimensionalIndexerInterface::class + ) + ); } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php new file mode 100644 index 0000000000000..0005ac8dea58a --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php @@ -0,0 +1,330 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query; + +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Framework\DB\Select; +use Magento\Framework\DB\Sql\ColumnValueExpression; +use Magento\Framework\Indexer\Dimension; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; + +/** + * Prepare base select for Product Price index limited by specified dimensions: website and customer group + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BaseFinalPrice +{ + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var string + */ + private $connectionName; + + /** + * @var JoinAttributeProcessor + */ + private $joinAttributeProcessor; + + /** + * @var \Magento\Framework\Module\Manager + */ + private $moduleManager; + + /** + * @var \Magento\Framework\Event\ManagerInterface + */ + private $eventManager; + + /** + * Mapping between dimensions and field in database + * + * @var array + */ + private $dimensionToFieldMapper = [ + WebsiteDimensionProvider::DIMENSION_NAME => 'pw.website_id', + CustomerGroupDimensionProvider::DIMENSION_NAME => 'cg.customer_group_id', + ]; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + + /** + * BaseFinalPrice constructor. + * @param \Magento\Framework\App\ResourceConnection $resource + * @param JoinAttributeProcessor $joinAttributeProcessor + * @param \Magento\Framework\Module\Manager $moduleManager + * @param string $connectionName + */ + public function __construct( + \Magento\Framework\App\ResourceConnection $resource, + JoinAttributeProcessor $joinAttributeProcessor, + \Magento\Framework\Module\Manager $moduleManager, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + $connectionName = 'indexer' + ) { + $this->resource = $resource; + $this->connectionName = $connectionName; + $this->joinAttributeProcessor = $joinAttributeProcessor; + $this->moduleManager = $moduleManager; + $this->eventManager = $eventManager; + $this->metadataPool = $metadataPool; + } + + /** + * @param Dimension[] $dimensions + * @param string $productType + * @param array $entityIds + * @return Select + * @throws \LogicException + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Select_Exception + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function getQuery(array $dimensions, string $productType, array $entityIds = []): Select + { + $connection = $this->getConnection(); + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $linkField = $metadata->getLinkField(); + + $select = $connection->select()->from( + ['e' => $this->getTable('catalog_product_entity')], + ['entity_id'] + )->joinInner( + ['cg' => $this->getTable('customer_group')], + array_key_exists(CustomerGroupDimensionProvider::DIMENSION_NAME, $dimensions) + ? sprintf( + '%s = %s', + $this->dimensionToFieldMapper[CustomerGroupDimensionProvider::DIMENSION_NAME], + $dimensions[CustomerGroupDimensionProvider::DIMENSION_NAME]->getValue() + ) : '', + ['customer_group_id'] + )->joinInner( + ['pw' => $this->getTable('catalog_product_website')], + 'pw.product_id = e.entity_id', + ['pw.website_id'] + )->joinInner( + ['cwd' => $this->getTable('catalog_product_index_website')], + 'pw.website_id = cwd.website_id', + [] + )->joinLeft( + // we need this only for BCC in case someone expects table `tp` to be present in query + ['tp' => $this->getTable('catalog_product_index_tier_price')], + 'tp.entity_id = e.entity_id AND' . + ' tp.customer_group_id = cg.customer_group_id AND tp.website_id = pw.website_id', + [] + )->joinLeft( + // calculate tier price specified as Website = `All Websites` and Customer Group = `Specific Customer Group` + ['tier_price_1' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_1.' . $linkField . ' = e.' . $linkField . ' AND tier_price_1.all_groups = 0' . + ' AND tier_price_1.customer_group_id = cg.customer_group_id AND tier_price_1.qty = 1' . + ' AND tier_price_1.website_id = 0', + [] + )->joinLeft( + // calculate tier price specified as Website = `Specific Website` + //and Customer Group = `Specific Customer Group` + ['tier_price_2' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_2.' . $linkField . ' = e.' . $linkField . ' AND tier_price_2.all_groups = 0 ' . + 'AND tier_price_2.customer_group_id = cg.customer_group_id AND tier_price_2.qty = 1' . + ' AND tier_price_2.website_id = pw.website_id', + [] + )->joinLeft( + // calculate tier price specified as Website = `All Websites` and Customer Group = `ALL GROUPS` + ['tier_price_3' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_3.' . $linkField . ' = e.' . $linkField . ' AND tier_price_3.all_groups = 1 ' . + 'AND tier_price_3.customer_group_id = 0 AND tier_price_3.qty = 1 AND tier_price_3.website_id = 0', + [] + )->joinLeft( + // calculate tier price specified as Website = `Specific Website` and Customer Group = `ALL GROUPS` + ['tier_price_4' => $this->getTable('catalog_product_entity_tier_price')], + 'tier_price_4.' . $linkField . ' = e.' . $linkField . ' AND tier_price_4.all_groups = 1' . + ' AND tier_price_4.customer_group_id = 0 AND tier_price_4.qty = 1' . + ' AND tier_price_4.website_id = pw.website_id', + [] + ); + + foreach ($dimensions as $dimension) { + if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) { + throw new \LogicException( + 'Provided dimension is not valid for Price indexer: ' . $dimension->getName() + ); + } + $select->where($this->dimensionToFieldMapper[$dimension->getName()] . ' = ?', $dimension->getValue()); + } + + if ($this->moduleManager->isEnabled('Magento_Tax')) { + $taxClassId = $this->joinAttributeProcessor->process($select, 'tax_class_id'); + } else { + $taxClassId = new \Zend_Db_Expr(0); + } + $select->columns(['tax_class_id' => $taxClassId]); + + $this->joinAttributeProcessor->process($select, 'status', Status::STATUS_ENABLED); + + $price = $this->joinAttributeProcessor->process($select, 'price'); + $specialPrice = $this->joinAttributeProcessor->process($select, 'special_price'); + $specialFrom = $this->joinAttributeProcessor->process($select, 'special_from_date'); + $specialTo = $this->joinAttributeProcessor->process($select, 'special_to_date'); + $currentDate = 'cwd.website_date'; + + $maxUnsignedBigint = '~0'; + $specialFromDate = $connection->getDatePartSql($specialFrom); + $specialToDate = $connection->getDatePartSql($specialTo); + $specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}"; + $specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}"; + $specialPriceExpr = $connection->getCheckSql( + "{$specialPrice} IS NOT NULL AND ({$specialFromExpr}) AND ({$specialToExpr})", + $specialPrice, + $maxUnsignedBigint + ); + $tierPrice = $this->getTotalTierPriceExpression($price); + $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint); + $finalPrice = $connection->getLeastSql([ + $price, + $specialPriceExpr, + $tierPriceExpr, + ]); + + $select->columns( + [ + //orig_price in catalog_product_index_price_final_tmp + 'price' => $connection->getIfNullSql($price, 0), + //price in catalog_product_index_price_final_tmp + 'final_price' => $connection->getIfNullSql($finalPrice, 0), + 'min_price' => $connection->getIfNullSql($finalPrice, 0), + 'max_price' => $connection->getIfNullSql($finalPrice, 0), + 'tier_price' => $tierPrice, + ] + ); + + $select->where("e.type_id = ?", $productType); + + if ($entityIds !== null) { + if (count($entityIds) > 1) { + $select->where(sprintf('e.entity_id BETWEEN %s AND %s', min($entityIds), max($entityIds))); + } else { + $select->where('e.entity_id = ?', $entityIds); + } + } + + /** + * throw event for backward compatibility + */ + $this->eventManager->dispatch( + 'prepare_catalog_product_index_select', + [ + 'select' => $select, + 'entity_field' => new ColumnValueExpression('e.entity_id'), + 'website_field' => new ColumnValueExpression('pw.website_id'), + 'store_field' => new ColumnValueExpression('cwd.default_store_id'), + ] + ); + + return $select; + } + + /** + * Get total tier price expression + * + * @param \Zend_Db_Expr $priceExpression + * @return \Zend_Db_Expr + */ + private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) + { + $maxUnsignedBigint = '~0'; + + return $this->getConnection()->getCheckSql( + implode( + ' AND ', + [ + 'tier_price_1.value_id is NULL', + 'tier_price_2.value_id is NULL', + 'tier_price_3.value_id is NULL', + 'tier_price_4.value_id is NULL' + ] + ), + 'NULL', + $this->getConnection()->getLeastSql([ + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_2', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_3', $priceExpression), + $maxUnsignedBigint + ), + $this->getConnection()->getIfNullSql( + $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression), + $maxUnsignedBigint + ), + ]) + ); + } + + /** + * Get tier price expression for table + * + * @param $tableAlias + * @param \Zend_Db_Expr $priceExpression + * @return \Zend_Db_Expr + */ + private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $priceExpression): \Zend_Db_Expr + { + return $this->getConnection()->getCheckSql( + sprintf('%s.value = 0', $tableAlias), + sprintf( + 'ROUND(%s * (1 - ROUND(%s.percentage_value * cwd.rate, 4) / 100), 4)', + $priceExpression, + $tableAlias + ), + sprintf('ROUND(%s.value * cwd.rate, 4)', $tableAlias) + ); + } + + /** + * Get connection + * + * return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \DomainException + */ + private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface + { + if ($this->connection === null) { + $this->connection = $this->resource->getConnection($this->connectionName); + } + + return $this->connection; + } + + /** + * Get table + * + * @param string $tableName + * @return string + */ + private function getTable($tableName) + { + return $this->resource->getTableName($tableName, $this->connectionName); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php new file mode 100644 index 0000000000000..888e68a817081 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\DB\Sql\Expression; + +/** + * Allows to join product attribute to Select. Used for build price index for specified dimension + */ +class JoinAttributeProcessor +{ + /** + * @var \Magento\Eav\Model\Config + */ + private $eavConfig; + + /** + * @var \Magento\Framework\EntityManager\MetadataPool + */ + private $metadataPool; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var string + */ + private $connectionName; + + /** + * @param \Magento\Eav\Model\Config $eavConfig + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param \Magento\Framework\App\ResourceConnection $resource + * @param string $connectionName + */ + public function __construct( + \Magento\Eav\Model\Config $eavConfig, + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + \Magento\Framework\App\ResourceConnection $resource, + $connectionName = 'indexer' + ) { + $this->eavConfig = $eavConfig; + $this->metadataPool = $metadataPool; + $this->resource = $resource; + $this->connectionName = $connectionName; + } + + /** + * @param Select $select + * @param string $attributeCode + * @param string|null $attributeValue + * @return \Zend_Db_Expr + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Select_Exception + */ + public function process(Select $select, $attributeCode, $attributeValue = null): \Zend_Db_Expr + { + $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode); + $attributeId = $attribute->getAttributeId(); + $attributeTable = $attribute->getBackend()->getTable(); + $connection = $this->resource->getConnection($this->connectionName); + $joinType = $attributeValue !== null ? 'join' : 'joinLeft'; + $productIdField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + if ($attribute->isScopeGlobal()) { + $alias = 'ta_' . $attributeCode; + $select->{$joinType}( + [$alias => $attributeTable], + "{$alias}.{$productIdField} = e.{$productIdField} AND {$alias}.attribute_id = {$attributeId}" . + " AND {$alias}.store_id = 0", + [] + ); + $whereExpression = new Expression("{$alias}.value"); + } else { + $dAlias = 'tad_' . $attributeCode; + $sAlias = 'tas_' . $attributeCode; + + $select->{$joinType}( + [$dAlias => $attributeTable], + "{$dAlias}.{$productIdField} = e.{$productIdField} AND {$dAlias}.attribute_id = {$attributeId}" . + " AND {$dAlias}.store_id = 0", + [] + ); + $select->joinLeft( + [$sAlias => $attributeTable], + "{$sAlias}.{$productIdField} = e.{$productIdField} AND {$sAlias}.attribute_id = {$attributeId}" . + " AND {$sAlias}.store_id = cwd.default_store_id", + [] + ); + $whereExpression = $connection->getCheckSql( + $connection->getIfNullSql("{$sAlias}.value_id", -1) . ' > 0', + "{$sAlias}.value", + "{$dAlias}.value" + ); + } + + if ($attributeValue !== null) { + $select->where("{$whereExpression} = ?", $attributeValue); + } + + return $whereExpression; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php new file mode 100644 index 0000000000000..5a055e5ed9603 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query\BaseFinalPrice; +use Magento\Framework\Indexer\DimensionalIndexerInterface; + +/** + * Simple Product Type Price Indexer + */ +class SimpleProductPrice implements DimensionalIndexerInterface +{ + /** + * @var BaseFinalPrice + */ + private $baseFinalPrice; + + /** + * @var IndexTableStructureFactory + */ + private $indexTableStructureFactory; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * @var string + */ + private $productType; + + /** + * @var BasePriceModifier + */ + private $basePriceModifier; + + /** + * @param BaseFinalPrice $baseFinalPrice + * @param IndexTableStructureFactory $indexTableStructureFactory + * @param TableMaintainer $tableMaintainer + * @param BasePriceModifier $basePriceModifier + * @param string $productType + */ + public function __construct( + BaseFinalPrice $baseFinalPrice, + IndexTableStructureFactory $indexTableStructureFactory, + TableMaintainer $tableMaintainer, + BasePriceModifier $basePriceModifier, + $productType = \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE + ) { + $this->baseFinalPrice = $baseFinalPrice; + $this->indexTableStructureFactory = $indexTableStructureFactory; + $this->tableMaintainer = $tableMaintainer; + $this->productType = $productType; + $this->basePriceModifier = $basePriceModifier; + } + + /** + * {@inheritdoc} + */ + public function executeByDimensions(array $dimensions, \Traversable $entityIds) + { + $this->tableMaintainer->createMainTmpTable($dimensions); + + $temporaryPriceTable = $this->indexTableStructureFactory->create([ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ]); + $select = $this->baseFinalPrice->getQuery($dimensions, $this->productType, iterator_to_array($entityIds)); + $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); + $this->tableMaintainer->getConnection()->query($query); + + $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds)); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php index 4775b96e3a448..179da06b59990 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php @@ -307,7 +307,7 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje } /** - * Get first col from from first row for option table + * Get first col from first row for option table * * @param string $tableName * @param int $optionId diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 91bb99ca971a7..ce0a9b6e461ce 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -256,7 +256,8 @@ protected function _saveValueTitles(AbstractModel $object) $object->unsetData('title'); } - if ($object->getTitle()) { + /*** Checking whether title is not null ***/ + if ($object->getTitle()!= null) { if ($existInCurrentStore) { if ($storeId == $object->getStoreId()) { $where = [ @@ -300,7 +301,7 @@ protected function _saveValueTitles(AbstractModel $object) } /** - * Get first col from from first row for option table + * Get first col from first row for option table * * @param string $tableName * @param int $optionId diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php index bcb6638b9cd25..83d59718400bd 100644 --- a/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php @@ -63,7 +63,7 @@ public function setItem(ItemInterface $item) : ConfiguredRegularPrice return $this; } - + /** * Price value of product with configured options. * @@ -73,7 +73,7 @@ public function getValue() { $basePrice = parent::getValue(); - return $this->item + return $this->item && $basePrice !== false ? $basePrice + $this->configuredOptions->getItemOptionsValue($basePrice, $this->item) : $basePrice; } diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php b/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php index ea8f6bbf39b31..226b94ceb1749 100644 --- a/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php +++ b/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - +declare(strict_types=1); namespace Magento\Catalog\Setup\Patch\Data; use Magento\Catalog\Setup\CategorySetupFactory; diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php b/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php new file mode 100644 index 0000000000000..f881b2f49f600 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Class EnableDirectiveParsing + * @package Magento\Catalog\Setup\Patch + */ +class EnableDirectiveParsing implements DataPatchInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * PatchInitial constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup + ) { + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $configTable = $this->moduleDataSetup->getTable('core_config_data'); + $select = $this->moduleDataSetup->getConnection()->select() + ->from($configTable) + ->where('path = ?', 'catalog/frontend/parse_url_directives'); + $config = $this->moduleDataSetup->getConnection()->fetchAll($select); + if (!empty($config)) { + $this->moduleDataSetup->getConnection()->update( + $configTable, + ['value' => '1'], + ['path = ?' => 'catalog/frontend/parse_url_directives', 'value IN (?)' => '0'] + ); + } + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php index f9d6abbc37493..a190bde2c6775 100644 --- a/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php +++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php @@ -156,6 +156,21 @@ private function processAttributeValues(array $attributeValueItems, $tableName) */ private function fetchAttributeValues($tableName) { + //filter store groups which have more than 1 store + $multipleStoresInWebsite = array_values( + array_reduce( + array_filter($this->getGroupedStoreViews(), function ($storeViews) { + return is_array($storeViews) && count($storeViews) > 1; + }), + 'array_merge', + [] + ) + ); + + if (count($multipleStoresInWebsite) < 1) { + return []; + } + $connection = $this->moduleDataSetup->getConnection(); $batchSelectIterator = $this->batchQueryGenerator->generate( 'value_id', @@ -184,9 +199,10 @@ private function fetchAttributeValues($tableName) self::ATTRIBUTE_WEBSITE ) ->where( - 'cpei.store_id <> ?', - self::GLOBAL_STORE_VIEW_ID - ) + 'cpei.store_id IN (?)', + $multipleStoresInWebsite + ), + 1000 ); foreach ($batchSelectIterator as $select) { diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml index 44c960dc37641..692487c1d60cd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AddSimpleProductToCart"> <arguments> <argument name="product" defaultValue="product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 7c04e9bd83d56..76f65381f43fa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Create a new category--> <actionGroup name="CreateCategory"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml index e7d9a63484bc6..a99420bcf95bb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Create a new root category--> <actionGroup name="AdminCreateRootCategory"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index db148b2cf3114..84231473b685d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Navigate to create product page from product grid page--> <actionGroup name="goToCreateProductPage"> <arguments> @@ -157,7 +157,7 @@ <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> <fillField userInput="{{simpleProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> - <click selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" stepKey="openCustomOptionsSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomOptionsSection"/> <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> <fillField userInput="option1" selector="{{AdminProductCustomizableOptionsSection.optionTitleInput}}" stepKey="fillOptionTitle"/> <click selector="{{AdminProductCustomizableOptionsSection.optionTypeOpenDropDown}}" stepKey="openTypeDropDown"/> @@ -181,6 +181,37 @@ </arguments> <conditionalClick selector="{{AdminProductFormSection.productFormTab('Related Products')}}" dependentSelector="{{AdminProductFormSection.productFormTabState('Related Products', 'closed')}}" visible="true" stepKey="openTab"/> <waitForPageLoad time="30" stepKey="waitForPageLoad"/> - <see selector="{{element}}" userInput="{{expectedText}}" stepKey="AssertText"/> + <see selector="{{element}}" userInput="{{expectedText}}" stepKey="assertText"/> </actionGroup> + + <!--Related products--> + <actionGroup name="addRelatedProductBySku"> + <arguments> + <argument name="sku"/> + </arguments> + <!--Scroll up to avoid error--> + <scrollTo selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" x="0" y="-100" stepKey="scrollTo"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedUpSellCrossSell"/> + <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.AddRelatedProductsButton}}" stepKey="clickAddRelatedProductButton"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminProductModalSlideGridSection.productGridXRowYColumnButton('1', '1')}}" stepKey="selectProduct"/> + <click selector="{{AdminAddRelatedProductsModalSection.AddSelectedProductsButton}}" stepKey="addRelatedProductSelected"/> + </actionGroup> + + <!--Add special price to product in Admin product page--> + <actionGroup name="AddSpecialPriceToProductActionGroup"> + <arguments> + <argument name="price" type="string" defaultValue="8"/> + </arguments> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitSpecialPrice"/> + <fillField userInput="{{price}}" selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="fillSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> + <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitForCloseModalWindow"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml index 3f4ee180fc65f..fd80838692065 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="navigateToCreatedProductAttribute"> <arguments> <argument name="ProductAttribute"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml index 33f4ccac2b98f..5948ca12dcf0f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssignAttributeToGroup"> <arguments> <argument name="group" type="string"/> @@ -34,4 +34,15 @@ <click selector="{{AdminProductAttributeSetActionSection.save}}" stepKey="clickSave"/> <see userInput="You saved the attribute set" selector="{{AdminMessagesSection.success}}" stepKey="successMessage"/> </actionGroup> + <actionGroup name="CreateDefaultAttributeSet"> + <!--Generic atrribute set creation--> + <arguments> + <argument name="label" type="string"/> + </arguments> + <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSets"/> + <waitForPageLoad stepKey="wait1"/> + <click selector="{{AdminProductAttributeSetGridSection.addAttributeSetBtn}}" stepKey="clickAddAttributeSet"/> + <fillField selector="{{AdminProductAttributeSetSection.name}}" userInput="{{label}}" stepKey="fillName"/> + <click selector="{{AdminProductAttributeSetSection.saveBtn}}" stepKey="clickSave1"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index 9d6af144b8f22..1bd9bb4a09c86 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Reset the product grid to the default view--> <actionGroup name="resetProductGridToDefaultView"> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> @@ -53,6 +53,18 @@ <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad" time="30"/> </actionGroup> + <!--Filter the product grid by the Name field--> + <actionGroup name="filterProductGridByName2"> + <arguments> + <argument name="name" type="string"/> + </arguments> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{name}}" stepKey="fillProductNameFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad" time="30"/> + </actionGroup> + <!--Filter the product grid by new from date filter--> <actionGroup name="filterProductGridBySetNewFromDate"> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml index 4eca49dc28b57..8b657fa1b8aab 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssertProductInStorefrontCategoryPage"> <arguments> <argument name="category"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml index 59c874b8481d3..391a1a7d670de 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssertProductInStorefrontProductPage"> <arguments> <argument name="product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml index 304f38e227960..f2a7a0acffefa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml @@ -6,7 +6,7 @@ */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CheckItemInLayeredNavigationActionGroup"> <arguments> <argument name="itemType"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml index e8097cfa4fffb..7373d5baea0c5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml @@ -6,12 +6,8 @@ */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - - +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateCustomRadioOptions"> - <!-- ActionGroup will add a single custom option to a product --> <!-- You must already be on the product creation page --> <arguments> @@ -39,8 +35,23 @@ <fillField stepKey="fillInValueTitle2" selector="{{AdminProductCustomizableOptionsSection.valueTitle}}" userInput="{{productOption2.title}}"/> <fillField stepKey="fillInValuePrice2" selector="{{AdminProductCustomizableOptionsSection.valuePrice}}" userInput="{{productOption2.price}}"/> + </actionGroup> - + <!--Add a custom option of type "file" to a product--> + <actionGroup name="AddProductCustomOptionFile"> + <arguments> + <argument name="option" defaultValue="ProductOptionFile"/> + </arguments> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" stepKey="waitForOption"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{option.title}}" stepKey="fillTitle"/> + <click selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}" stepKey="openTypeSelect"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionType('File')}}" stepKey="selectTypeFile"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPrice}}" stepKey="waitForElements"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice}}" userInput="{{option.price}}" stepKey="fillPrice"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensions}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml index ae9dc0557a9bd..7bb9aa60ca628 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="MoveCategoryActionGroup"> <arguments> <argument name="childCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml index 07fba7cc6be06..8f89a85e14892 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="OpenEditProductOnBackendActionGroup"> <arguments> <argument name="product" defaultValue="product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml index e8794ab895c6b..c460dcbfbec91 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="OpenProductFromCategoryPageActionGroup"> <arguments> <argument name="category"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml index 53acfe2b4372d..2f9d38516bd05 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="RestoreLayoutSetting"> <selectOption selector="{{DefaultLayoutsSection.categoryLayout}}" userInput="No layout updates" stepKey="selectNoLayoutUpdates1" after="expandDefaultLayouts"/> <selectOption selector="{{DefaultLayoutsSection.productLayout}}" userInput="No layout updates" stepKey="selectNoLayoutUpdates2" before="clickSaveConfig"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml index 943fe803232e6..53e7ea3589d1e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="searchAndMultiSelectActionGroup"> <arguments> <argument name="dropDownSelector" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml index 5fbc9c5d7fcad..113e108577aa8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SearchForProductOnBackendActionGroup"> <arguments> <argument name="product" defaultValue="product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml index 105a5c58788de..c7ae52d2b37c3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Click Add to Cart button in storefront product page--> <actionGroup name="StorefrontAddToCartCustomOptionsProductPageActionGroup"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 25f059c84f4ef..c980c43b8f3af 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Go to storefront category product page by given parameters --> <actionGroup name="GoToStorefrontCategoryPageByParameters"> <arguments> @@ -45,4 +45,9 @@ <!-- @TODO: MAGETWO-80272 Move to Magento_Checkout --> <seeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="AssertAddToCart" /> </actionGroup> -</actionGroups> \ No newline at end of file + + <actionGroup name="StorefrontSwitchCategoryViewToListMode"> + <click selector="{{StorefrontCategoryMainSection.modeListButton}}" stepKey="switchCategoryViewToListMode"/> + <waitForElement selector="{{StorefrontCategoryMainSection.CategoryTitle}}" time="30" stepKey="waitForCategoryReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml index 7af1cacfb3da8..04e15da91777c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Add Product to Compare from the category page and check message --> <actionGroup name="StorefrontAddCategoryProductToCompareActionGroup"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml index eb672cd162e82..5f0d03597dab1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check the simple product on the product page --> <actionGroup name="StorefrontCheckSimpleProduct"> <arguments> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml index d688a1dc2844e..82042975d5fb8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml @@ -6,18 +6,15 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Click Add to Cart button in storefront product page--> <actionGroup name="addToCartFromStorefrontProductPage"> <arguments> <argument name="productName"/> </arguments> <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> - <waitForElementVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAdding"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdding"/> - <waitForElementVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAdded"/> <waitForElementNotVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdded"/> - <waitForElementVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAddToCart}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAddToCart"/> <waitForPageLoad stepKey="waitForPageLoad"/> <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{productName}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 42351741d9fa8..5c79c321c9431 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultCategory" type="category"> <data key="name" unique="suffix">simpleCategory</data> <data key="name_lwr" unique="suffix">simplecategory</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml index 8ae57f9239902..8a26b6babdbbc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="CONST" type="CONST"> <data key="one">1</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index e93138fecfd47..389c41abf0bd1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -6,7 +6,7 @@ */ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomAttributeCategoryUrlKey" type="custom_attribute"> <data key="attribute_code">url_key</data> <data key="value" unique="suffix">category</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml index 2423383bc19f7..a46d40c62c76e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml @@ -7,9 +7,13 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductAttributeFrontendLabel" type="FrontendLabel"> <data key="store_id">0</data> <data key="label" unique="suffix">attribute</data> </entity> + <entity name="ProductAttributeFrontendLabelTwo" type="FrontendLabel"> + <data key="store_id">0</data> + <data key="label" unique="suffix">attributeTwo</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml index c674a8fc144ce..1f4b1470098e2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="TestImageContent" type="ImageContent"> <data key="base64_encoded_data">/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAGAAYAMBIgACEQEDEQH/xACXAAEBAAMBAQEBAAAAAAAAAAAABgMEBQgCAQcQAAEDAQUFBgQDCQAAAAAAAAABAgMEBQYRFpESMTZV0QchcnOzwhMUIkEygaE1QlFSYXGCsbIBAAEFAQAAAAAAAAAAAAAAAAACAwQGBwERAAECAwMLBAMBAAAAAAAAAAEAAgMEERMhkRQxMzRBUVJTcXKxBRJhoSKBwUL/2gAMAwEAAhEDEQA/AP7+AYKysp7Po5aurlbFBEmL3u3NQ6ASaBdArcFnBN5/urzqn0d0Gf7q86p9HdCRkUzy3YFOWEXhOCpATef7q86p9HdBn+6vOqfR3QMimeW7AosIvCcFSAm8/wB1edU+jugz/dXnVPo7oGRTPLdgUWEXhOCpATef7q86p9HdBn+6vOqfR3QMimeW7AosIvCcFSA1bPtGktWiZWUM7Z6d6qjZG7lwXBf1Q2iO5paaOFCmyCDQoTd/uBLX8n3IUhN3+4EtfyfchIk9Zh9w8pyBpW9QvN4Bwbcsujis+pq2Q4Tq5HbW0u9XJj3Y4fc0ibjPgQjEY0GgJNTS4brj/FaIz3Q2FwFafNP4V3gc1aWz7FY+rjhVrsNjBrlcrsV3Iir/ABPxtqzRyM+boJKeJ7kakm2jkRV3Yom4TlbYf4xrnfFSBuqaCn7ouWwbc+4/FT90XTBz57RlbVvpqWjdUSRoiyfWjUbju71MUlqSyWdVPjpnsqIUVJI3ORFZ3fix+4OnoLSRU3V2HZnANKEjcEGOwVG74OxdUGjZM1RNQROqIlYuw3Zcr9pXpgn1f0xN4kQYgiww8bU4xwe0OG1eg+y7gCg8cvqOLEjuy7gCg8cvqOLEzT1HXIvcfKq0zpn9ShN3+4EtfyfchSE3f7gS1/J9yCJPWYfcPKTA0reoXm85l4P2HUf4/wDSHTPmSOOZiskY17F3tcmKKaXMwjGgvhj/AECMQrTFZ72ObvC5lvxq+gjeivRsUzXvVn4kb34qmpozxWc+NjVtWtqPiOREjbMj1Vf7YFHvMMdLTxP244ImP/maxEUhzMhaxC8UvABrXZuoR9pmLL+9xddfvXNrfkVtJyPqJaOpRiL8VHbKPT8+5THFVS1FnWnE+VKhsUbmsmamG3i1e78jsSwQzoiTRRyIm5HtRf8AZ9MjZGxGMY1rU/damCHTJPMQuDgAa5q31G0VpdnrnuRYO9xNaA1+/r9rUsmeGazqdscrHuZExHo1cVauH30U3THFBDBtfBijj2t+w1Ex0MhMgMcyG1r843J+GC1oDs69B9l3AFB45fUcWJHdl3AFB45fUcWJm3qOuRe4+VV5nTP6lCbv9wJa/k+5CkJu/wBwJa/k+5BEnrMPuHlJgaVvULzeADUlbUAAIQAAhAACF6D7LuAKDxy+o4sSO7LuAKDxy+o4sTMPUdci9x8qqTOmf1KE3f7gS1/J9yFITd/uBLX8n3IIk9Zh9w8pMDSt6hebwAakragABCAAEIAAQvQfZdwBQeOX1HFiR3ZdwBQeOX1HFiZh6jrkXuPlVSZ0z+pQwVlHT2hRy0lXE2WCVMHsduchnBEBINQmQaXhTeQLq8lp9XdRkC6vJafV3UpASMtmeY7Epy3i8RxU3kC6vJafV3UZAuryWn1d1KQBlszzHYlFvF4jipvIF1eS0+ruoyBdXktPq7qUgDLZnmOxKLeLxHFTeQLq8lp9XdRkC6vJafV3UpAGWzPMdiUW8XiOK1bPs6ksqiZR0MDYKdiqrY27kxXFf1U2gCO5xcauNSmySTUr/9k=</data> <data key="type">image/jpeg</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index f67370dcff296..b367cdcab9d8b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="productAttributeWysiwyg" type="ProductAttribute"> <data key="attribute_code" unique="suffix">attribute</data> <data key="frontend_input">textarea</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml index 60b38812e4ced..98c9a70e6aad4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ApiProductAttributeMediaGalleryEntryTestImage" type="ProductAttributeMediaGalleryEntry"> <data key="media_type">image</data> <data key="label" unique="suffix">Test Image </data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml index 15c2dc8bbebca..c575f1a5db82f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="productAttributeOption1" type="ProductAttributeOption"> <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> <data key="label" unique="suffix">option1</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml index 68c0a54ff88fc..68f51559a9f31 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="AddToDefaultSet" type="ProductAttributeSet"> <var key="attributeCode" entityKey="attribute_code" entityType="ProductAttribute"/> <data key="attributeSetId">4</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 46ad529e5e89e..9ae551b69d388 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultProduct" type="product"> <data key="sku" unique="suffix">testSku</data> <data key="type_id">simple</data> @@ -197,6 +197,15 @@ <data key="filename">magento-logo</data> <data key="file_extension">png</data> </entity> + <entity name="TestImageNew" type="image"> + <data key="title" unique="suffix">magento-again</data> + <data key="price">1.00</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">magento-again.jpg</data> + <data key="filename">magento-again</data> + <data key="file_extension">jpg</data> + </entity> <entity name="ProductWithUnicode" type="product"> <data key="sku" unique="suffix">霁产品</data> <data key="type_id">simple</data> @@ -210,6 +219,32 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="productWithHTMLEntityOne" type="product"> + <data key="sku" unique="suffix">SimpleOne™Product</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">SimpleOne™Product</data> + <data key="price">50.00</data> + <data key="urlKey" unique="suffix">testurlkey</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> + <entity name="productWithHTMLEntityTwo" type="product"> + <data key="sku" unique="suffix">SimpleTwo霁产品<カネボウPro</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">SimpleTwo霁产品<カネボウPro</data> + <data key="price">50.00</data> + <data key="urlKey" unique="suffix">testurlkey</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> <entity name="defaultVirtualProduct" type="product"> <data key="sku" unique="suffix">virtualProduct</data> <data key="type_id">virtual</data> @@ -280,6 +315,10 @@ <requiredEntity type="product_option">ProductOptionDateTime</requiredEntity> <requiredEntity type="product_option">ProductOptionTime</requiredEntity> </entity> + <entity name="productWithOptions2" type="product"> + <var key="sku" entityType="product" entityKey="sku" /> + <requiredEntity type="product_option">ProductOptionDropDownWithLongValuesTitle</requiredEntity> + </entity> <entity name="ApiVirtualProductWithDescription" type="product"> <data key="sku" unique="suffix">api-virtual-product</data> <data key="type_id">virtual</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml index 88ff2bbace47a..6e532637fb6d3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="EavStockItem" type="product_extension_attribute"> <requiredEntity type="stock_item">Qty_1000</requiredEntity> </entity> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml index b123800a6cc84..ea0bcafe56c48 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="PriceFilterRange" type="filter"> <data key="from">10</data> <data key="to">100</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml index 95905eb90d926..ca5024920ad40 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductOptionField" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionField</data> @@ -59,6 +59,15 @@ <requiredEntity type="product_option_value">ProductOptionValueDropdown1</requiredEntity> <requiredEntity type="product_option_value">ProductOptionValueDropdown2</requiredEntity> </entity> + <entity name="ProductOptionDropDownWithLongValuesTitle" type="product_option"> + <var key="product_sku" entityType="product" entityKey="sku" /> + <data key="title">OptionDropDownWithLongTitles</data> + <data key="type">drop_down</data> + <data key="sort_order">4</data> + <data key="is_require">true</data> + <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle1</requiredEntity> + <requiredEntity type="product_option_value">ProductOptionValueDropdownLongTitle2</requiredEntity> + </entity> <entity name="ProductOptionRadiobutton" type="product_option"> <var key="product_sku" entityType="product" entityKey="sku" /> <data key="title">OptionRadioButtons</data> @@ -112,4 +121,4 @@ <data key="price">0.00</data> <data key="price_type">percent</data> </entity> -</entities> \ No newline at end of file +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml index 815f8cf16809b..d16a201cd9ecc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductOptionValueDropdown1" type="product_option_value"> <data key="title">OptionValueDropDown1</data> <data key="sort_order">1</data> @@ -50,4 +50,16 @@ <data key="price">2</data> <data key="price_type">fixed</data> </entity> -</entities> \ No newline at end of file + <entity name="ProductOptionValueDropdownLongTitle1" type="product_option_value"> + <data key="title">Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj11111Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj11111</data> + <data key="sort_order">1</data> + <data key="price">10</data> + <data key="price_type">fixed</data> + </entity> + <entity name="ProductOptionValueDropdownLongTitle2" type="product_option_value"> + <data key="title">Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj22222Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj22222</data> + <data key="sort_order">2</data> + <data key="price">20</data> + <data key="price_type">percent</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml index 4fae51de86c45..39ecc2d440fc2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Qty_1000" type="stock_item"> <data key="qty">1000</data> <data key="is_in_stock">true</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml index a703e56beda01..ce964e2d71503 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Option1Store0" type="StoreLabel"> <data key="store_id">0</data> <data key="label">option1</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml index 0880315db5d6b..ae491aefc10cf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateCategory" dataType="category" type="create" auth="adminOauth" url="/V1/categories" method="POST"> <contentType>application/json</contentType> <object key="category" dataType="category"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml index aed9b7a979836..a37bb36eb6597 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateCustomAttribute" dataType="custom_attribute" type="create"> <field key="attribute_code">string</field> <field key="value">string</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml index d8410593cb5b4..7faac6c3b6d3d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateEmptyExtensionAttribute" dataType="empty_extension_attribute" type="create"> </operation> <operation name="UpdateEmptyExtensionAttribute" dataType="empty_extension_attribute" type="update"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml index d0bcbd3e5db97..063b8c2e5ac63 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateFrontendLabel" dataType="FrontendLabel" type="create"> <field key="store_id">integer</field> <field key="label">string</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml index 212de2b39d363..9ece47c01fca3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProduct" dataType="product" type="create" auth="adminOauth" url="/V1/products" method="POST"> <contentType>application/json</contentType> <object dataType="product" key="product"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml index 93396352ba506..1e9aa3bc219e9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductAttribute" dataType="ProductAttribute" type="create" auth="adminOauth" url="/V1/products/attributes" method="POST"> <contentType>application/json</contentType> <object dataType="ProductAttribute" key="attribute"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml index 8033e8c33a349..521e864702e57 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductAttributeMediaGalleryEntry" dataType="ProductAttributeMediaGalleryEntry" type="create" auth="adminOauth" url="/V1/products/{sku}/media" method="POST"> <contentType>application/json</contentType> <object key="entry" dataType="ProductAttributeMediaGalleryEntry"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml index 176afa8d58d7c..467ff9a48eb77 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductAttributeOption" dataType="ProductAttributeOption" type="create" auth="adminOauth" url="/V1/products/attributes/{attribute_code}/options" method="POST"> <contentType>application/json</contentType> <object dataType="ProductAttributeOption" key="option"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml index eef82b07aaf4f..6f04c48e79254 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="AddProductAttributeToAttributeSet" dataType="ProductAttributeSet" type="create" auth="adminOauth" url="/V1/products/attribute-sets/attributes" method="POST"> <contentType>application/json</contentType> <field key="attributeSetId">integer</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml index 8d0d1e66c81e3..127a754c88808 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductExtensionAttribute" dataType="product_extension_attribute" type="create"> <field key="stock_item">stock_item</field> </operation> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml index 5e631b2ea3a28..a2fcbb1417d6f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductLink" dataType="product_link" type="create"> <field key="sku">string</field> <field key="link_type">string</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml index 07ea02f5b7aee..90888463ef8a2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductLinkExtensionAttribute" dataType="product_link_extension_attribute" type="create"> <contentType>application/json</contentType> <field key="qty">integer</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml index 56b3ee25ef735..450ea99b9d016 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductLinks" dataType="product_links" type="create" auth="adminOauth" url="/V1/products/{sku}/links" method="POST"> <contentType>application/json</contentType> <array key="items"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml index adc5a33507af6..6464c2988ad25 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductOption" dataType="product_option" type="create"> <field key="product_sku">string</field> <field key="option_id">integer</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml index f4273f5796830..bce77bc3a2612 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductOptionValue" dataType="product_option_value" type="create"> <field key="title">string</field> <field key="sort_order">integer</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml index e7e79d69055c6..6ec5f2c8051ea 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateStockItem" dataType="stock_item" type="create"> <field key="qty">integer</field> <field key="is_in_stock">boolean</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml index abb9b003dc59e..584ba5eebb551 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateStoreLabel" dataType="StoreLabel" type="create"> <field key="store_id">integer</field> <field key="label">string</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml index c568e52b2ab3c..aa120491ece5d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateValidationRule" dataType="validation_rule" type="create"> <field key="key">string</field> <field key="value">string</field> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml index cfefa8cb2c4bc..e1c8e5c75e9ac 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCategoryEditPage" url="catalog/category/edit/id/{{categoryId}}/" area="admin" module="Catalog" parameterized="true"> <section name="AdminCategorySidebarActionSection"/> <section name="AdminCategoryMainActionsSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml index 7cabe0e18f0b6..9349e188430f4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCategoryPage" url="catalog/category/" area="admin" module="Catalog"> <section name="AdminCategorySidebarActionSection"/> <section name="AdminCategoryMainActionsSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml index b04aff5f161da..fab87f90f86dd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="ProductAttributePage" url="catalog/product_attribute/new/" area="admin" module="Catalog"> <section name="AdminCreateProductAttributeSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml index a5de7453d9c23..e6aafa53601a6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductAttributeGridPage" url="catalog/product_attribute" area="admin" module="Catalog"> <section name="AdminProductAttributeGridSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml index 4034f2ab075d4..3e89cbc8262ce 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductAttributeSetEditPage" url="catalog/product_set/edit/id" area="admin" module="Catalog"> <section name="AdminProductAttributeSetEditSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml index 0d879768eb494..d55e71adca24b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductAttributeSetGridPage" url="catalog/product_set/" area="admin" module="ProductAttributeSet"> <section name="AdminProductAttributeSetGridSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml index 4918041d2cd88..66475a93b75b1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="ProductAttributesEditPage" url="catalog/product_action_attribute/edit/" area="admin" module="Catalog"> <section name="AdminEditProductAttributesSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml index 35fa00efcfe8e..fc776b49ba213 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> <section name="AdminProductFormSection"/> <section name="AdminProductFormActionSection"/> @@ -16,5 +16,6 @@ <section name="AdminAddProductsToOptionPanel"/> <section name="AdminProductMessagesSection"/> <section name="AdminProductFormRelatedUpSellCrossSellSection"/> + <section name="AdminProductFormAdvancedPricingSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml index 9312d4dfcfbe9..c9debf8bf3b3d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductEditPage" url="catalog/product/edit/id/{{productId}}/" area="admin" module="Magento_Catalog" parameterized="true"> <!-- This page object only exists for the url. Use the AdminProductCreatePage for selectors. --> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml index 66cd691176268..a6edf06f2c1b7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductIndexPage" url="catalog/product/index" area="admin" module="Magento_Catalog"> <section name="AdminProductGridActionSection" /> <section name="AdminProductGridFilterSection" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml index 742b46fcaf7ed..012aeaaf14e70 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="ProductCatalogPage" url="/catalog/product/" area="admin" module="Magento_Catalog"> <section name="ProductCatalogPageSection"/> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml index c5b9fe869558e..469c153d38b88 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCategoryPage" url="/{{var1}}.html" area="storefront" module="Catalog" parameterized="true"> <section name="StorefrontCategoryMainSection"/> <section name="WYSIWYGToolbarSection"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml index f0599a021d4c4..5451d92022496 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontProductComparePage" url="catalog/product_compare/index" module="Magento_Catalog" area="storefront"> <section name="StorefrontProductCompareMainSection" /> </page> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml index 8fd59585938be..5aaa78822af08 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontProductPage" url="/{{var1}}.html" area="storefront" module="Catalog" parameterized="true"> <section name="StorefrontProductInfoMainSection" /> <section name="StorefrontProductInfoDetailsSection" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml index 4541ad25af231..069a8b28698d1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminAddProductsToOptionPanel"> <element name="addSelectedProducts" type="button" selector=".product_form_product_form_bundle-items_modal button.action-primary" timeout="30"/> <element name="filters" type="button" selector=".product_form_product_form_bundle-items_modal button[data-action='grid-filter-expand']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml index 3ed3763da19d6..8b69a44993f17 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryBasicFieldSection"> <element name="IncludeInMenu" type="checkbox" selector="input[name='include_in_menu']"/> <element name="includeInMenuLabel" type="text" selector="input[name='include_in_menu']+label"/> @@ -16,9 +16,12 @@ <element name="enableCategoryLabel" type="text" selector="input[name='is_active']+label"/> <element name="enableUseDefault" type="checkbox" selector="input[name='use_default[is_active]']"/> <element name="CategoryNameInput" type="input" selector="input[name='name']"/> + <element name="RequiredFieldIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=name]>.admin__field-label span'), ':after').getPropertyValue('content');"/> + <element name="RequiredFieldIndicatorColor" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=name]>.admin__field-label span'), ':after').getPropertyValue('color');"/> <element name="categoryNameUseDefault" type="checkbox" selector="input[name='use_default[name]']"/> <element name="ContentTab" type="input" selector="input[name='name']"/> <element name="FieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> + <element name="panelFieldControl" type="input" selector='//aside//div[@data-index="{{arg1}}"]/descendant::*[@name="{{arg2}}"]' parameterized="true"/> </section> <section name="CategoryContentSection"> <element name="SelectFromGalleryBtn" type="button" selector="//label[text()='Select from Gallery']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml index 59537274f23c9..ed0325394d591 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryContentSection"> <element name="sectionHeader" type="button" selector="div[data-index='content']" timeout="30"/> <element name="uploadButton" type="button" selector="//*[@class='file-uploader-area']/label[text()='Upload']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml index 60a6d852bf6ef..009110a729bde 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryMainActionsSection"> <element name="SaveButton" type="button" selector=".page-actions-inner #save" timeout="30"/> <element name="DeleteButton" type="button" selector=".page-actions-inner #delete" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml index 1214cfd2eb224..fee86ca1caa29 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryMessagesSection"> <element name="SuccessMessage" type="text" selector=".message-success"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryModalSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryModalSection.xml index 03b9d76778555..85b8dc894a139 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryModalSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryModalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryModalSection"> <element name="message" type="text" selector="aside.confirm div.modal-content"/> <element name="title" type="text" selector="aside.confirm .modal-header .modal-title"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml index 540a97fd04e36..6b754dcc5d482 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryProductsGridSection"> <element name="rowProductId" type="text" selector="#catalog_category_products_table tbody tr:nth-of-type({{row}}) .col-id" parameterized="true"/> <element name="rowProductName" type="text" selector="#catalog_category_products_table tbody tr:nth-of-type({{row}}) .col-name" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml index dc254bdf15982..3c05f72ff1597 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryProductsSection"> <element name="sectionHeader" type="button" selector="div[data-index='assign_products']" timeout="30"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml index 35852abe3505e..b5d5d61f6468b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySEOSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategorySEOSection"> <element name="SectionHeader" type="button" selector="div[data-index='search_engine_optimization']" timeout="30"/> <element name="UrlKeyInput" type="input" selector="input[name='url_key']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarActionSection.xml index e53a9989d661c..0a1901f1fdaf8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarActionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategorySidebarActionSection"> <element name="AddRootCategoryButton" type="button" selector="#add_root_category_button" timeout="30"/> <element name="AddSubcategoryButton" type="button" selector="#add_subcategory_button" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index 524fac78bc1c1..ef6fb99e88eed 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategorySidebarTreeSection"> <element name="collapseAll" type="button" selector=".tree-actions a:first-child"/> <element name="expandAll" type="button" selector=".tree-actions a:last-child"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryWarningMessagesPopupSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryWarningMessagesPopupSection.xml index 82b3b76df3d2e..4c16e9081f4c6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryWarningMessagesPopupSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryWarningMessagesPopupSection.xml @@ -6,7 +6,7 @@ */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCategoryWarningMessagesPopupSection"> <element name="warningMessage" type="text" selector=".modal-inner-wrap .modal-content .message.message-notice"/> <element name="cancelButton" type="button" selector=".modal-inner-wrap .action-secondary"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index e1eef15e9d476..b83676c2e1033 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -7,21 +7,25 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AttributePropertiesSection"> + <element name="propertiesTab" type="button" selector="#product_attribute_tabs_main"/> <element name="DefaultLabel" type="input" selector="#attribute_label"/> <element name="InputType" type="select" selector="#frontend_input"/> <element name="ValueRequired" type="select" selector="#is_required"/> <element name="AdvancedProperties" type="button" selector="#advanced_fieldset-wrapper"/> <element name="DefaultValue" type="input" selector="#default_value_text"/> - <element name="Save" type="button" selector="#save"/> - <element name="SaveAndEdit" type="button" selector="#save_and_edit_button"/> + <element name="Scope" type="select" selector="#is_global"/> + <element name="Save" type="button" selector="#save" timeout="30"/> + <element name="SaveAndEdit" type="button" selector="#save_and_edit_button" timeout="30"/> <element name="TinyMCE4" type="button" selector="//span[text()='Default Value']/parent::label/following-sibling::div//div[@class='mce-branding-powered-by']"/> <element name="checkIfTabOpen" selector="//div[@id='advanced_fieldset-wrapper' and not(contains(@class,'opened'))]" type="button"/> + <element name="useInLayeredNavigation" type="select" selector="#is_filterable"/> </section> <section name="StorefrontPropertiesSection"> <element name="StoreFrontPropertiesTab" selector="#product_attribute_tabs_front" type="button"/> <element name="EnableWYSIWYG" type="select" selector="#enabled"/> + <element name="useForPromoRuleConditions" type="select" selector="#is_used_for_promo_rules"/> </section> <section name="WYSIWYGProductAttributeSection"> <element name="ShowHideBtn" type="button" selector="#toggledefault_value_texteditor"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml index 703e9e7ec70ac..99dfddcc776d3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditProductAttributesSection"> <element name="AttributeName" type="text" selector="#name"/> <element name="ChangeAttributeNameToggle" type="checkbox" selector="#toggle_name"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml index 820e03a0f5e98..160948f8f1f2c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml @@ -7,13 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAttributeGridSection"> - <element name="AttributeCode" type="text" selector="//td[contains(text(),'{{var1}}')]" parameterized="true"/> + <element name="AttributeCode" type="text" selector="//td[contains(text(),'{{var1}}')]" parameterized="true" timeout="30"/> <element name="createNewAttributeBtn" type="button" selector="button[data-index='add_new_attribute_button']"/> <element name="GridFilterFrontEndLabel" type="input" selector="#attributeGrid_filter_frontend_label"/> <element name="Search" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> <element name="ResetFilter" type="button" selector="button[data-action='grid-filter-reset']" timeout="30"/> - <element name="FirstRow" type="button" selector="//*[@id='attributeGrid_table']/tbody/tr[1]"/> + <element name="FirstRow" type="button" selector="//*[@id='attributeGrid_table']/tbody/tr[1]" timeout="30"/> + <element name="FilterByAttributeCode" type="input" selector="#attributeGrid_filter_attribute_code"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetActionSection.xml index 4c309584d4d56..e165b51ef180e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetActionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAttributeSetActionSection"> <element name="save" type="button" selector="button[title='Save']" timeout="30"/> <element name="reset" type="button" selector="button[title='Reset']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml index a2193bcafbb01..0814c7ea7dc3e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetEditSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAttributeSetEditSection"> <!-- Groups Column --> <element name="groupTree" type="block" selector="#tree-div1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml index 08724222a3885..b906e2fa9084b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAttributeSetGridSection"> <element name="filter" type="input" selector="#setGrid_filter_set_name"/> <element name="searchBtn" type="button" selector="#container button[title='Search']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml index 9e320d9e8b08d..8f635214ffffd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductAttributeSetSection"> <element name="name" type="input" selector="#attribute_set_name"/> <element name="basedOn" type="select" selector="#skeleton_set"/> @@ -29,6 +29,6 @@ </section> <section name="ModifyAttributes"> <!-- Parameter is the attribute name --> - <element name="nthExistingAttribute" type="select" selector="//*[text()='{{attributeName}}']/../..//select" parameterized="true"/> + <element name="nthExistingAttribute" type="select" selector="//*[text()='{{attributeName}}']/../../..//select" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml index 81290bf281a56..337cf0527dd4e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductCategoryCreationSection"> <element name="firstExampleProduct" type="button" selector=".data-row:nth-of-type(1)"/> <element name="newCategory" type="button" selector="//button/span[text()='New Category']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml index b73c630d6d963..a2ad155672a1a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml @@ -7,10 +7,11 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductContentSection"> <element name="sectionHeader" type="button" selector="div[data-index='content']" timeout="30"/> <element name="descriptionTextArea" type="textarea" selector="#product_form_description"/> <element name="shortDescriptionTextArea" type="textarea" selector="#product_form_short_description"/> + <element name="sectionHeaderIfNotShowing" type="button" selector="//div[@data-index='content']//div[contains(@class, '_hide')]"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index 8a6b5bf5eb842..052195ec1aaa7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -7,27 +7,26 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductCustomizableOptionsSection"> <element name="checkIfCustomizableOptionsTabOpen" type="text" selector="//span[text()='Customizable Options']/parent::strong/parent::*[@data-state-collapsible='closed']"/> - <element name="customezableOptions" type="text" selector="//strong[contains(@class, 'admin__collapsible-title')]/span[text()='Customizable Options']"/> + <element name="customizableOptions" type="text" selector="//strong[contains(@class, 'admin__collapsible-title')]/span[text()='Customizable Options']"/> <element name="useDefaultOptionTitle" type="text" selector="[data-index='options'] tr.data-row [data-index='title'] [name^='options_use_default']"/> <element name="useDefaultOptionTitleByIndex" type="text" selector="[data-index='options'] [data-index='values'] tr[data-repeat-index='{{var1}}'] [name^='options_use_default']" parameterized="true"/> <element name="addOptionBtn" type="button" selector="button[data-index='button_add']"/> - <element name="fillOptionTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Title']/parent::label/parent::div//input[@class='admin__control-text']" parameterized="true"/> + <element name="fillOptionTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Title']/parent::label/parent::div/parent::div//input[@class='admin__control-text']" parameterized="true"/> <element name="optionTitleInput" type="input" selector="input[name='product[options][0][title]']"/> <element name="optionTypeOpenDropDown" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-select"/> <element name="optionTypeTextField" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-menu._active li li"/> <element name="maxCharactersInput" type="input" selector="input[name='product[options][0][max_characters]']"/> - - <element name="checkSelect" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Type']/parent::label/parent::div//div[@data-role='selected-option']" parameterized="true"/> - <element name="checkDropDown" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//parent::label/parent::div//li[@class='admin__action-multiselect-menu-inner-item']//label[text()='Drop-down']" parameterized="true"/> + <element name="checkSelect" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Type']/parent::label/parent::div/parent::div//div[@data-role='selected-option']" parameterized="true"/> + <element name="checkDropDown" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//parent::label/parent::div/parent::div//li[@class='admin__action-multiselect-menu-inner-item']//label[text()='Drop-down']" parameterized="true"/> <element name="clickAddValue" type="button" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tfoot//button" parameterized="true"/> - <element name="fillOptionValueTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody/tr[@data-repeat-index='{{var2}}']//span[text()='Title']/parent::label/parent::div//div[@class='admin__field-control']/input" parameterized="true"/> + <element name="fillOptionValueTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody/tr[@data-repeat-index='{{var2}}']//span[text()='Title']/parent::label/parent::div/parent::div//div[@class='admin__field-control']/input" parameterized="true"/> <element name="fillOptionValuePrice" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody/tr[@data-repeat-index='{{var2}}']//span[text()='Price']/parent::label/parent::div//div[@class='admin__control-addon']/input" parameterized="true"/> - <element name="clickSelectPriceType" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody//tr[@data-repeat-index='{{var2}}']//span[text()='Price Type']/parent::label/parent::div//select" parameterized="true"/> - <element name="checkboxUseDefaultTitle" type="checkbox" selector="//span[text()='Option Title']/parent::label/parent::div/div//input[@type='checkbox']"/> + <element name="clickSelectPriceType" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody//tr[@data-repeat-index='{{var2}}']//span[text()='Price Type']/parent::label/parent::div/parent::div//select" parameterized="true"/> + <element name="checkboxUseDefaultTitle" type="checkbox" selector="//span[text()='Option Title']/parent::label/parent::div/parent::div/div//input[@type='checkbox']"/> <element name="checkboxUseDefaultOption" type="checkbox" selector="//table[@data-index='values']//tbody//tr[@data-repeat-index='{{var1}}']//div[@class='admin__field-control']//input[@type='checkbox']" parameterized="true"/> <!-- Elements that make it easier to select the most recently added element --> @@ -38,5 +37,10 @@ <element name="addValue" type="button" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@data-action='add_new_row']" /> <element name="valueTitle" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__control-table')]//tbody/tr[last()]//*[@data-index='title']//input" /> <element name="valuePrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__control-table')]//tbody/tr[last()]//*[@data-index='price']//input" /> + + <element name="optionPrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][price]']"/> + <element name="optionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][price_type]']"/> + <element name="optionSku" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][sku]']"/> + <element name="optionFileExtensions" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][file_extension]']"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml index 4c6c1020e190d..ed08c84cdb6f8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFiltersSection"> <element name="filtersButton" type="button" selector="#container > div > div.admin__data-grid-header > div:nth-child(1) > div.data-grid-filters-actions-wrap > div > button"/> <element name="clearFiltersButton" type="button" selector="//div[@class='admin__data-grid-header']//button[@class='action-tertiary action-clear']" timeout="10"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml index ad6b5feb4e09e..afbaba41a9bb7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml @@ -7,13 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormActionSection"> <element name="backButton" type="button" selector="#back" timeout="30"/> <element name="saveButton" type="button" selector="#save-button" timeout="30"/> <element name="saveArrow" type="button" selector="button[data-ui-id='save-button-dropdown']" timeout="30"/> - <!--<element name="saveAndClose" type="button" selector="span[title='Save & Close']" timeout="30"/>--> - <element name="saveAndClose" type="button" selector="span[id='save_and_close']" timeout="30"/> + <element name="saveAndClose" type="button" selector="span[title='Save & Close']" timeout="30"/> <element name="changeStoreButton" type="button" selector="#store-change-button" timeout="10"/> <element name="selectStoreView" type="button" selector="//ul[@data-role='stores-list']/li/a[normalize-space(.)='{{var1}}']" timeout="10" parameterized="true"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml new file mode 100644 index 0000000000000..0a1804aa284dc --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminProductFormAdvancedPricingSection"> + <element name="customerGroupPriceAddButton" type="button" selector="[data-action='add_new_row']" timeout="30"/> + <element name="customerGroupPriceDeleteButton" type="button" selector="[data-action='remove_row']" timeout="30"/> + <element name="advancedPricingCloseButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-close" timeout="30"/> + <element name="productTierPriceWebsiteSelect" type="select" selector="[name='product[tier_price][{{var1}}][website_id]']" parameterized="true"/> + <element name="productTierPriceCustGroupSelect" type="select" selector="[name='product[tier_price][{{var1}}][cust_group]']" parameterized="true"/> + <element name="productTierPriceQtyInput" type="input" selector="[name='product[tier_price][{{var1}}][price_qty]']" parameterized="true"/> + <element name="productTierPriceValueTypeSelect" type="select" selector="[name='product[tier_price][{{var1}}][value_type]']" parameterized="true"/> + <element name="productTierPriceFixedPriceInput" type="input" selector="[name='product[tier_price][{{var1}}][price]']" parameterized="true"/> + <element name="productTierPricePercentageValuePriceInput" type="input" selector="[name='product[tier_price][{{var1}}][percentage_value]']" parameterized="true"/> + <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> + <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="5"/> + <element name="save" type="button" selector="#save-button"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormChangeStoreSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormChangeStoreSection.xml index 8c9e92d912bf3..04e5445c8ab63 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormChangeStoreSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormChangeStoreSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormChangeStoreSection"> <element name="storeSelector" type="button" selector="//a[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="acceptButton" type="button" selector="button[class='action-primary action-accept']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index dd297bf301fa0..cfd7e5ed3b8d5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -6,13 +6,18 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormSection"> <element name="attributeSet" type="select" selector="div[data-index='attribute_set_id'] .admin__field-control"/> <element name="attributeSetFilter" type="input" selector="div[data-index='attribute_set_id'] .admin__field-control input" timeout="30"/> <element name="attributeSetFilterResult" type="input" selector="div[data-index='attribute_set_id'] .action-menu-item._last" timeout="30"/> + <element name="attributeSetFilterResultByName" type="text" selector="//label/span[text() = '{{var}}']" timeout="30" parameterized="true"/> <element name="productName" type="input" selector=".admin__field[data-index=name] input"/> + <element name="RequiredNameIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=name]>.admin__field-label span'), ':after').getPropertyValue('content');"/> + <element name="RequiredSkuIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=sku]>.admin__field-label span'), ':after').getPropertyValue('content');"/> <element name="productSku" type="input" selector=".admin__field[data-index=sku] input"/> + <element name="enableProductAttributeLabel" type="text" selector="//span[text()='Enable Product']/parent::label"/> + <element name="enableProductAttributeLabelWrapper" type="text" selector="//span[text()='Enable Product']/parent::label/parent::div"/> <element name="productStatus" type="checkbox" selector="input[name='product[status]']"/> <element name="enableProductLabel" type="checkbox" selector="input[name='product[status]']+label"/> <element name="productStatusUseDefault" type="checkbox" selector="input[name='use_default[status]']"/> @@ -39,11 +44,12 @@ <element name="visibility" type="select" selector="//select[@name='product[visibility]']"/> <element name="visibilityUseDefault" type="checkbox" selector="//input[@name='use_default[visibility]']"/> <element name="divByDataIndex" type="input" selector="div[data-index='{{var}}']" parameterized="true"/> + <element name="setProductAsNewFrom" type="input" selector="input[name='product[news_from_date]']"/> + <element name="setProductAsNewTo" type="input" selector="input[name='product[news_to_date]']"/> <element name="attributeLabelByText" type="text" selector="//*[@class='admin__field']//span[text()='{{attributeLabel}}']" parameterized="true"/> </section> <section name="ProductInWebsitesSection"> <element name="sectionHeader" type="button" selector="div[data-index='websites']" timeout="30"/> - <!--<element name="websites" type="checkbox" selector="input[name='product[website_ids][{{var1}}]']" parameterized="true"/>--> <element name="website" type="checkbox" selector="//label[contains(text(), '{{var1}}')]/parent::div//input[@type='checkbox']" parameterized="true"/> </section> <section name="ProductDesignSection"> @@ -58,7 +64,7 @@ </section> <section name="ProductWYSIWYGSection"> <element name="Switcher" type="button" selector="//select[@id='dropdown-switcher']"/> - <element name="v4" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 4.3.6']" /> + <element name="v436" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 4.3.6']" /> <element name="v3" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 3.6(Deprecated)']" /> <element name="TinymceDescription3" type ="button" selector="//span[text()='Description']" /> <element name="SaveConfig" type ="button" selector="#save" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml index 4ce9580405a97..3b74041684017 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridActionSection.xml @@ -7,8 +7,9 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridActionSection"> + <element name="addProductBtn" type="button" selector="#add_new_product-button" timeout="30"/> <element name="addProductToggle" type="button" selector=".action-toggle.primary.add"/> <element name="addSimpleProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-simple']" timeout="30"/> <element name="addGroupedProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-grouped']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridConfirmActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridConfirmActionSection.xml index d8567df81b6b3..5bf73076e14de 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridConfirmActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridConfirmActionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridConfirmActionSection"> <element name="title" type="text" selector=".modal-popup.confirm h1.modal-title"/> <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 4683576bf9516..611f12a39b510 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridFilterSection"> <element name="filters" type="button" selector="button[data-action='grid-filter-expand']"/> <element name="clearAll" type="button" selector=".admin__data-grid-header .admin__data-grid-filters-current._show .action-clear" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridPaginationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridPaginationSection.xml index 9ef89e1260fa4..fbcfabfa02fa1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridPaginationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridPaginationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridPaginationSection"> <element name="perPageDropdown" type="select" selector=".admin__data-grid-pager-wrap .selectmenu"/> <element name="perPageOption" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//button[text()='{{label}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index de3c2b731a3d5..a7e20e22f1ddc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridSection"> <element name="loadingMask" type="text" selector=".admin__data-grid-loading-mask[data-component*='product_listing']"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> @@ -28,5 +28,6 @@ <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)"/> <element name="productGridCheckboxOnRow" type="checkbox" selector="//*[@id='container']//tr[{{row}}]/td[1]//input" parameterized="true"/> <element name="productGridNameProduct" type="input" selector="//tbody//tr//td//div[contains(., '{{var1}}')]" parameterized="true" timeout="30"/> + <element name="selectRowBasedOnName" type="input" selector="//td/div[text()='{{var1}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml index fc6ccea20d3c2..7341a6ded7a09 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridTableHeaderSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridTableHeaderSection"> <element name="id" type="button" selector=".//*[@class='sticky-header']/following-sibling::*//th[@class='data-grid-th _sortable _draggable _{{order}}']/span[text()='ID']" parameterized="true"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml index ce10b1e52aeb0..eca0cb6f02ea1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductImagesSection"> <element name="productImagesToggle" type="button" selector="div[data-index=gallery] .admin__collapsible-title"/> <element name="imageFileUpload" type="input" selector="#fileupload"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml index 5f2e6bd6cf721..59fbeee142dfe 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> <element name="errorMessage" type="text" selector=".message.message-error.error"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml index bef213e6cdae0..adc3a753f06f5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductModalSlideGridSection"> <element name="productGridXRowYColumnButton" type="input" selector=".modal-slide table.data-grid tr.data-row:nth-child({{row}}) td:nth-child({{column}})" parameterized="true" timeout="30"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml index 636a7b5c85e8d..e90d806805f7c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml @@ -7,11 +7,15 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormRelatedUpSellCrossSellSection"> <element name="AddRelatedProductsButton" type="button" selector="button[data-index='button_related']" timeout="30"/> <element name="relatedProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='related']"/> <element name="upSellProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='upsell']"/> <element name="crossSellProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='crosssell']"/> + <element name="relatedDropdown" type="block" selector="//div[@data-index='related']" timeout="30"/> + <element name="relatedDependent" type="block" selector="//div[@data-index='related']//div[contains(@class, '_show')]"/> + <element name="selectedRelatedProduct" type="block" selector="//span[@data-index='name']"/> + <element name="removeRelatedProduct" type="button" selector="//span[text()='Related Products']//..//..//..//span[text()='{{productName}}']//..//..//..//..//..//button[@class='action-delete']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml index 1d49d05363612..c545fcd408831 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductSEOSection"> <element name="sectionHeader" type="button" selector="div[data-index='search-engine-optimization']" timeout="30"/> <element name="urlKeyInput" type="input" selector="input[name='product[url_key]']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml index 3048f0e3f5669..bf8812b3acef5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminUpdateAttributesSection"> <element name="saveButton" type="button" selector="button[title='Save']" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml index 631cb36e16817..ddec4428f90e2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryFilterSection"> <element name="CategoryFilter" type="button" selector="//main//div[@class='filter-options']//div[contains(text(), 'Category')]"/> <element name="CategoryByName" type="button" selector="//main//div[@class='filter-options']//li[@class='item']//a[contains(text(), '{{var1}}')]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index 2a6003d837b2e..d484abf2069ff 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryMainSection"> <element name="modeListButton" type="button" selector="#mode-list"/> <element name="CategoryTitle" type="text" selector="#page-title-heading span"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml index 625c6946c757a..7ea74d7913758 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryProductSection"> <element name="ProductTitleByNumber" type="button" selector="//main//li[{{var1}}]//a[@class='product-item-link']" parameterized="true"/> <element name="ProductPriceByNumber" type="text" selector="//main//li[{{var1}}]//span[@class='price']" parameterized="true"/> @@ -23,6 +23,11 @@ <element name="ProductImageByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//img[@class='product-image-photo']" parameterized="true"/> <element name="ProductImageBySrc" type="text" selector=".products-grid img[src*='{{pattern}}']" parameterized="true"/> <element name="ProductInfoByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//div[@class='product-item-info']" parameterized="true"/> + <element name="productPriceFinal" type="text" selector="//span[@data-price-type='finalPrice']//span[@class='price'][contains(.,'{{var1}}')]" parameterized="true"/> + <element name="productPriceOld" type="text" selector="//span[@data-price-type='oldPrice']//span[@class='price'][contains(., '{{var1}}')]" parameterized="true"/> + <element name="productPriceLabel" type="text" selector="//span[@class='price-label'][contains(text(),'{{var1}}')]" parameterized="true"/> + <element name="productPriceLinkAfterLabel" type="text" selector="//span[@class='price-label'][contains(text(),'{{var1}}')]/following::span[contains(text(), '{{var2}}')]" parameterized="true"/> + <element name="ProductAddToCartByName" type="button" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//a[contains(@class, 'tocart')]" parameterized="true"/> <element name="ProductAddToCompareByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//a[contains(@class, 'tocompare')]" parameterized="true"/> <element name="ProductImageByNameAndSrc" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//img[contains(@src, '{{src}}')]" parameterized="true"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml index 9cd35f65c297a..0599a42bfd7a9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml @@ -6,7 +6,7 @@ */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategorySidebarSection"> <element name="filterOptionsTitle" type="text" selector="//div[@class='filter-options-title' and contains(text(), '{{var1}}')]" parameterized="true"/> <element name="filterOptions" type="text" selector=".filter-options-content .items"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml index 2b44bf1db7efd..e063b5fc8c1b7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryTopToolbarSection.xml @@ -7,10 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryTopToolbarSection"> <element name="gridMode" type="button" selector=".//*[@class='toolbar toolbar-products'][1]//*[@id='mode-grid']" timeout="30"/> <element name="listMode" type="button" selector=".//*[@class='toolbar toolbar-products'][1]//*[@id='mode-list']" timeout="30"/> <element name="sortByDropdown" type="select" selector=".//*[@class='toolbar toolbar-products'][1]//*[@id='sorter']" timeout="30"/> + <element name="sortDirectionAsc" type="button" selector=".//*[@class='toolbar toolbar-products'][1]//a[contains(@class, 'sort-asc')]" timeout="30"/> + <element name="sortDirectionDesc" type="button" selector=".//*[@class='toolbar toolbar-products'][1]//a[contains(@class, 'sort-desc')]" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontComparisonSidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontComparisonSidebarSection.xml index 0fdda3eaae952..d097d6bbc4626 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontComparisonSidebarSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontComparisonSidebarSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontComparisonSidebarSection"> <element name="Compare" type="button" selector="//main//div[contains(@class, 'block-compare')]//a[contains(@class, 'action compare')]"/> <element name="ClearAll" type="button" selector="//main//div[contains(@class, 'block-compare')]//a[contains(@class, 'action clear')]"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml index cf956004ae498..1c937637ad823 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontFooterSection"> <element name="switchStoreButton" type="button" selector="#switcher-store-trigger"/> <element name="storeLink" type="button" selector="//ul[@class='dropdown switcher-dropdown']//a[contains(text(),'{{var1}}')]" parameterized="true" timeout="30"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml index 6b0130eefc39b..509ad2b8f849c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontHeaderSection"> <element name="NavigationCategoryByName" type="button" selector="//nav//a[span[contains(., '{{var1}}')]]" parameterized="true"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml index 1a9406b9975e6..dea1b2a5af752 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontMessagesSection"> <element name="success" type="text" selector="div.message-success.success.message"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml index ad575b640bd20..e8f35fc6787b7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml @@ -6,7 +6,7 @@ */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontNavigationSection"> <element name="topCategory" type="button" selector="//a[contains(@class,'level-top')]/span[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="subCategory" type="button" selector="//ul[contains(@class,'submenu')]//span[contains(text(),'{{var1}}')]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml index e15723582dbf0..f4db37b677584 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProducRelatedProductsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductRelatedProductsSection"> <element name="relatedProductsActionsHeaderText" type="text" selector=".block.related .block-actions" /> <element name="relatedProductsListSectionText" type="text" selector=".block.related .products.wrapper.grid.products-grid.products-related" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml index 65d6b7c5f61cb..98dc5e764fd77 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductActionSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductActionSection"> <element name="quantity" type="input" selector="#qty"/> <element name="addToCart" type="button" selector="#product-addtocart-button"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml index 728f9a5a174cd..ad31be6b277ee 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductCompareMainSection"> <element name="PageName" type="text" selector="//*[@id='maincontent']//h1//span"/> <element name="ProductLinkByName" type="button" selector="//*[@id='product-comparison']//tr//strong[@class='product-item-name']/a[contains(text(), '{{var1}}')]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml index 40f49fc2cc77b..0745c0d0819a0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoDetailsSection.xml @@ -7,8 +7,9 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoDetailsSection"> <element name="productNameForReview" type="text" selector=".legend.review-legend>strong" /> + <element name="detailsTab" type="button" selector="#tab-label-description-title" /> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 7e1bec48aeebc..b93a70559fc4a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -7,13 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="stock" type="input" selector=".stock.available"/> <element name="productName" type="text" selector=".base"/> <element name="productSku" type="text" selector=".product.attribute.sku>.value"/> <element name="productPriceLabel" type="text" selector=".price-label"/> <element name="productPrice" type="text" selector="div.price-box.price-final_price"/> + <element name="qty" type="input" selector="#qty"/> <element name="specialPrice" type="text" selector=".special-price"/> <element name="updatedPrice" type="text" selector="div.price-box.price-final_price [data-price-type='finalPrice'] .price"/> <element name="oldPrice" type="text" selector=".old-price"/> @@ -63,5 +64,10 @@ <element name="productAddToCompare" type="button" selector="a.action.tocompare"/> <element name="productOptionDropDownTitle" type="text" selector="//label[contains(.,'{{var1}}')]" parameterized="true"/> <element name="productOptionDropDownOptionTitle" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//select//option[contains(.,'{{var2}}')]" parameterized="true"/> + + <!-- Tier price selectors --> + <element name="productTierPriceByForTextLabel" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}][contains(text(),'Buy {{var2}} for')]" parameterized="true"/> + <element name="productTierPriceAmount" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}]//span[contains(text(), '{{var2}}')]" parameterized="true"/> + <element name="productTierPriceSavePercentageAmount" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}]//span[contains(@class, 'percent')][contains(text(), '{{var2}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml index 0273b39f48aba..83c3ca5348606 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductMediaSection"> <element name="imageFile" type="text" selector="//*[@class='product media']//img[contains(@src, '{{filename}}')]" parameterized="true"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml index fc2102e073de3..ee687fa62da93 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMoreInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductMoreInformationSection"> <element name="moreInformation" type="button" selector="#tab-label-additional-title" timeout="30"/> <element name="moreInformationTextArea" type="textarea" selector="#additional"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml index 632ee2dea68eb..c87af1224ed30 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml @@ -7,11 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductPageSection"> <element name="qtyInput" type="button" selector="input.input-text.qty"/> - <element name="addToCartBtn" type="button" selector="button.action.tocart.primary"/> + <element name="addToCartBtn" type="button" selector="button.action.tocart.primary" timeout="30"/> <element name="successMsg" type="button" selector="div.message-success"/> + <element name="errorMsg" type="button" selector="div.message-error"/> + <element name="alertMessage" type="text" selector=".page.messages [role=alert]"/> + <element name="messagesBlock" type="text" selector=".page.messages"/> <element name="addToWishlist" type="button" selector="//a[@class='action towishlist']" timeout="30"/> <element name="customTextOptionInput" type="input" selector=".input-text.product-custom-option"/> <element name="charCounter" type="text" selector=".character-counter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml index c9b6e033a2fd8..88a39a9087bb3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultImageSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml index add917199e2eb..3f857c258924f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddRemoveProductImageVirtualProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml new file mode 100644 index 0000000000000..5456cb02e74ca --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add default product video for a Simple Product"/> + <description value="Admin should be able to add default product video for a Simple Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-111"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="adminProductIndexPageAdd"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml new file mode 100644 index 0000000000000..f48c352c5290a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoVirtualProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add default product video for a Virtual Product"/> + <description value="Admin should be able to add default product video for a Virtual Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-109"/> + <group value="Catalog"/> + </annotations> + + <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> + + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageForCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageForCategoryTest.xml index 6ee72877a0da0..8ac0cfa512b03 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageForCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageForCategoryTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageForCategoryTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml index 479247ade8cb2..50d192a27e46d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageToWYSIWYGCatalogTest"> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml index d6e055c43322a..a280433e315f7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageToWYSIWYGProductTest"> <annotations> <features value="Catalog"/> @@ -16,6 +16,9 @@ <description value="Admin should be able to add image to WYSIWYG Editor on Product Page"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84375"/> + <skip> + <issueId value="MAGETWO-94438"/> + </skip> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> @@ -39,7 +42,7 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoading2" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn1" /> <see selector="{{ProductDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn1" /> - <see selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" userInput="Add Selected" stepKey="seeAddSelectedBtn1" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn" /> <click selector="{{ProductDescriptionWYSIWYGToolbarSection.CreateFolder}}" stepKey="createFolder1"/> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.FolderName}}" stepKey="waitForPopUp1" /> <fillField selector="{{ProductDescriptionWYSIWYGToolbarSection.FolderName}}" userInput="{{ImageFolder.name}}" stepKey="fillFolderName1" /> @@ -60,6 +63,7 @@ <click selector="{{ProductDescriptionWYSIWYGToolbarSection.confirmDelete}}" stepKey="confirmDelete1" /> <waitForElementNotVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForImageDeleted1" /> <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="dontSeeImage1" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn1" /> <attachFile selector="{{ProductDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload1.value}}" stepKey="uploadImage2"/> <waitForLoadingMaskToDisappear stepKey="waitForLoading6" /> <waitForElementVisible selector="{{ProductDescriptionWYSIWYGToolbarSection.image(ImageUpload1.value)}}" stepKey="waitForUploadImage2" /> @@ -78,7 +82,7 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoading8" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn2" /> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn2" /> - <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" userInput="Add Selected" stepKey="seeAddSelectedBtn2" /> + <dontSeeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn2" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage3"/> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage3" /> <waitForLoadingMaskToDisappear stepKey="waitForLoading9" /> @@ -88,6 +92,7 @@ <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.DeleteSelectedBtn}}" stepKey="clickDeleteSelected2" /> <waitForText userInput="OK" stepKey="waitForConfirm3" /> <click selector="{{ProductShortDescriptionWYSIWYGToolbarSection.confirmDelete}}" stepKey="confirmDelete2" /> + <dontSeeElement selector="{{ProductDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn3" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage4"/> <waitForLoadingMaskToDisappear stepKey="waitForLoading10" /> <waitForElementVisible selector="{{ProductShortDescriptionWYSIWYGToolbarSection.image(ImageUpload3.value)}}" stepKey="waitForUploadImage4" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml new file mode 100644 index 0000000000000..72c0a4a51901a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml @@ -0,0 +1,272 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminApplyTierPriceToProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Apply tier price to a product"/> + <title value="You should be able to apply tier price to a product."/> + <description value="You should be able to apply tier price to a product."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-68921"/> + <group value="product"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createSimpleUSCustomer"> + <field key="group_id">1</field> + </createData> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleUSCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex1"/> + <waitForPageLoad time="30" stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Case: Group Price--> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct1"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton1"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForCustomerGroupPriceAddButton1"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroupAllGroupsQty1PriceDiscountAnd10percent"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="1" stepKey="fillProductTierPriceQtyInput1"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceValueTypeSelect('0')}}" userInput="Discount" stepKey="selectProductTierPriceValueType1"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('0')}}" userInput="10" stepKey="selectProductTierPricePriceInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton1"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin1"> + <argument name="Customer" value="$$createSimpleUSCustomer$$" /> + </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage1"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('90')}}" stepKey="assertProductFinalPriceIs90_1"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceLabel_1"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceOld('100')}}" stepKey="assertRegularPriceAmount_1"/> + <amOnPage url="customer/account/logout/" stepKey="logoutCustomer1"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad2"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage2"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad3"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('90')}}" stepKey="assertProductFinalPriceIs90_2"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceLabel_2"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceOld('100')}}" stepKey="assertRegularPriceAmount_2"/> + <!--Case: Tier Price for General Customer Group--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex2"/> + <waitForPageLoad time="30" stepKey="waitForProductPageToLoad1"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct2"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage2"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton2"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForcustomerGroupPriceAddButton2"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" time="30" stepKey="waitForSelectCustomerGroupNameAttribute1"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="General" stepKey="selectCustomerGroupGeneral"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton2"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct2"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage3"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad4"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('100')}}" stepKey="assertProductFinalPriceIs100_1"/> + <dontSeeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceLabel_3"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin2"> + <argument name="Customer" value="$$createSimpleUSCustomer$$" /> + </actionGroup> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage4"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad5"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('90')}}" stepKey="assertProductFinalPriceIs90_3"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('Regular Price')}}" stepKey="assertRegularPriceLabel_4"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceOld('100')}}" stepKey="assertRegularPriceAmount_3"/> + <!--Case: Tier Price applied if Product quantity meets Tier Price Condition--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex3"/> + <waitForPageLoad time="30" stepKey="waitForProductPageToLoad2"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct3"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage3"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton3"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForcustomerGroupPriceAddButton3"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" stepKey="waitForSelectCustomerGroupNameAttribute2"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="ALL GROUPS" stepKey="selectCustomerGroupAllGroups"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="15" stepKey="fillProductTierPriceQtyInput15"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickToLoseFocusOnRequiredInputElement"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroupAllGroupsQty20PriceDiscountAnd18percent2"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('1')}}" userInput="20" stepKey="fillProductTierPriceQtyInput20"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceValueTypeSelect('1')}}" userInput="Discount" stepKey="selectProductTierPriceValueType2"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('1')}}" userInput="18" stepKey="selectProductTierPricePriceInput18"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton3"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct3"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage5"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad6"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('100')}}" stepKey="assertProductFinalPriceIs100_2"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('As low as')}}" stepKey="assertAsLowAsPriceLabel_1"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLinkAfterLabel('As low as', '82')}}" stepKey="assertPriceAfterAsLowAsLabel_1"/> + <amOnPage url="customer/account/logout/" stepKey="logoutCustomer2"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad7"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="navigateToCategoryPage6"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad8"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceFinal('100')}}" stepKey="assertProductFinalPriceIs100_3"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLabel('As low as')}}" stepKey="assertAsLowAsPriceLabel_2"/> + <seeElement selector="{{StorefrontCategoryProductSection.productPriceLinkAfterLabel('As low as', '82')}}" stepKey="assertPriceAfterAsLowAsLabel_2"/> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="goToProductPage1"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad9"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('1', '15')}}" stepKey="assertProductTierPriceByForTextLabelForFirstRow1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('2', '20')}}" stepKey="assertProductTierPriceByForTextLabelForSecondRow1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceAmount('1', '90')}}" stepKey="assertProductTierPriceAmountForFirstRow1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceAmount('2', '82')}}" stepKey="assertProductTierPriceAmountForSecondRow1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceSavePercentageAmount('1', '10')}}" stepKey="assertProductTierPriceSavePercentageAmountForFirstRow1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceSavePercentageAmount('2', '18')}}" stepKey="assertProductTierPriceSavePercentageAmountForSecondRow1"/> + <fillField userInput="10" selector="{{StorefrontProductInfoMainSection.qty}}" stepKey="fillProductQuantity1"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToCheckoutFromMinicart"/> + <seeInField userInput="10" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField10"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField1"/> + <assertEquals message="Shopping cart should contain subtotal $1,000" stepKey="assertSubtotalField1"> + <expectedResult type="string">$1,000.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField1</actualResult> + </assertEquals> + <fillField userInput="15" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="fillProductQuantity2"/> + <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickUpdateShoppingCartButton1"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField2"/> + <assertEquals message="Shopping cart should contain subtotal $1,350" stepKey="assertSubtotalField2"> + <expectedResult type="string">$1,350.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField2</actualResult> + </assertEquals> + <fillField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="fillProductQuantity3"/> + <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickUpdateShoppingCartButton2"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField3"/> + <assertEquals message="Shopping cart should contain subtotal $1,640" stepKey="assertSubtotalField3"> + <expectedResult type="string">$1,640.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField3</actualResult> + </assertEquals> + <!--Tier Price is changed in Shopping Cart and is changed on Product page if Tier Price parameters are changed in Admin--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex4"/> + <waitForPageLoad time="30" stepKey="waitForProductPageToLoa4"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct4"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage4"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton4"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForcustomerGroupPriceAddButton4"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('1')}}" userInput="25" stepKey="selectProductTierPricePercentageValue2"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton4"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct4"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage1"/> + <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad1"/> + <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField4"/> + <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField4"> + <expectedResult type="string">$1,500.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField4</actualResult> + </assertEquals> + <grabTextFrom selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal1"/> + <assertEquals message="Shopping cart summary section should contain subtotal $1,500" stepKey="assertSubtotalFieldFromCheckoutCartSummarySection1"> + <expectedResult type="string">$1,500.00</expectedResult> + <actualResult type="variable">grabTextFromCheckoutCartSummarySectionSubtotal1</actualResult> + </assertEquals> + <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart1"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="waitForminiCartSubtotalField1"/> + <grabTextFrom selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="grabTextFromMiniCartSubtotalField"/> + <assertEquals message="Mini shopping cart should contain subtotal $1,500" stepKey="assertSubtotalFieldFromMiniShoppingCart1"> + <expectedResult type="string">$1,500.00</expectedResult> + <actualResult type="variable">grabTextFromMiniCartSubtotalField</actualResult> + </assertEquals> + <amOnPage url="{{AdminSalesConfigPage.url('#sales_msrp-link')}}" stepKey="navigateToAdminSalesConfigPageMAPTab1"/> + <waitForPageLoad time="30" stepKey="waitForAdminSalesConfigPageLoad1"/> + <uncheckOption selector="{{AdminSalesConfigSection.enableMAPUseSystemValue}}" stepKey="uncheckMAPUseSystemValue"/> + <selectOption selector="{{AdminSalesConfigSection.enableMAPSelect}}" userInput="Yes" stepKey="setEnableMAPYes"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig1"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigSuccessMessage1"/> + <actionGroup ref="ClearCacheActionGroup" stepKey="flushCache1"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage2"/> + <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad2"/> + <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20_2"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField5"/> + <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField5"> + <expectedResult type="string">$1,500.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField5</actualResult> + </assertEquals> + <amOnPage url="{{AdminSalesConfigPage.url('#sales_msrp-link')}}" stepKey="navigateToAdminSalesConfigPageMAPTab2"/> + <waitForPageLoad time="30" stepKey="waitForAdminSalesConfigPageLoad2"/> + <selectOption selector="{{AdminSalesConfigSection.enableMAPSelect}}" userInput="No" stepKey="setEnableMAPNo"/> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig2"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigSuccessMessage2"/> + <actionGroup ref="ClearCacheActionGroup" stepKey="flushCache2"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage3"/> + <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad3"/> + <seeInField userInput="20" selector="{{CheckoutCartProductSection.ProductQuantityByName($$createSimpleProduct.name$$)}}" stepKey="seeInQtyField20_3"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField6"/> + <assertEquals message="Shopping cart should contain subtotal $1,500" stepKey="assertSubtotalField6"> + <expectedResult type="string">$1,500.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField6</actualResult> + </assertEquals> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="goToProductPage2"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad10"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('1', '15')}}" stepKey="assertProductTierPriceByForTextLabelForFirstRow2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('2', '20')}}" stepKey="assertProductTierPriceByForTextLabelForSecondRow2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceAmount('1', '90')}}" stepKey="assertProductTierPriceAmountForFirstRow2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceAmount('2', '75')}}" stepKey="assertProductTierPriceAmountForSecondRow2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceSavePercentageAmount('1', '10')}}" stepKey="assertProductTierPriceSavePercentageAmountForFirstRow2"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productTierPriceSavePercentageAmount('2', '25')}}" stepKey="assertProductTierPriceSavePercentageAmountForSecondRow2"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex5"/> + <waitForPageLoad time="30" stepKey="waitForProductPageToLoad3"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct5"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfPage5"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton5"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceDeleteButton}}" stepKey="waitForcustomerGroupPriceDeleteButton"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceDeleteButton}}" stepKey="deleteFirstRowOfCustomerGroupPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceDeleteButton}}" stepKey="deleteSecondRowOfCustomerGroupPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton5"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct5"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage6"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton6"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForcustomerGroupPriceAddButton5"/> + <dontSeeElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" stepKey="dontSeeQtyInputOfFirstRow"/> + <dontSeeElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('1')}}" stepKey="dontSeeQtyInputOfSecondRow"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="closeAdvancedPricingPopup"/> + <waitForElementVisible selector="{{AdminProductFormSection.productPrice}}" stepKey="waitForAdminProductFormSectionProductPriceInput"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="200" stepKey="fillProductPrice200"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage4"/> + <waitForPageLoad time="30" stepKey="waitForShoppingCartPagePageLoad4"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productSubtotalByName($$createSimpleProduct.name$$)}}" stepKey="grabTextFromSubtotalField7"/> + <assertEquals message="Shopping cart should contain subtotal $4,000" stepKey="assertSubtotalField7"> + <expectedResult type="string">$4,000.00</expectedResult> + <actualResult type="variable">grabTextFromSubtotalField7</actualResult> + </assertEquals> + <grabTextFrom selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal2"/> + <assertEquals message="Shopping cart summary section should contain subtotal $4,000" stepKey="assertSubtotalFieldFromCheckoutCartSummarySection2"> + <expectedResult type="string">$4,000.00</expectedResult> + <actualResult type="variable">grabTextFromCheckoutCartSummarySectionSubtotal2</actualResult> + </assertEquals> + <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart2"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="waitForminiCartSubtotalField2"/> + <grabTextFrom selector="{{StorefrontMinicartSection.miniCartSubtotalField}}" stepKey="grabTextFromMiniCartSubtotalField2"/> + <assertEquals message="Mini shopping cart should contain subtotal $4,000" stepKey="assertSubtotalFieldFromMiniShoppingCart2"> + <expectedResult type="string">$4,000.00</expectedResult> + <actualResult type="variable">grabTextFromMiniCartSubtotalField2</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml index aed667db1f7b2..4261721d36064 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAssignProductAttributeToAttributeSetTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml index d5483f772f028..a5150a0fb7f24 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryFromProductPageTest.xml @@ -7,11 +7,12 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCategoryFromProductPageTest"> <annotations> <features value="Catalog"/> <stories value="Create/Edit Category in Admin"/> + <title value="Admin should be able to create category from the product page"/> <description value="Admin should be able to create category from the product page" /> <severity value="AVERAGE"/> <testCaseId value="MC-234"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml index 7e3e2cd918f5e..bfb9557910642 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCategoryTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml index 038c8fd2263f0..713e1b7d6dfd1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateProductCustomAttributeSet"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml index 0340eea852a4c..95d74b9653113 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateProductDuplicateUrlkeyTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryAndSubcategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryAndSubcategoriesTest.xml index dbe5a90d592da..11d919ddefa2c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryAndSubcategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryAndSubcategoriesTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateRootCategoryAndSubcategoriesTest"> <annotations> <features value="Catalog"/> + <stories value="Create categories"/> <title value="Admin should be able to create a Root Category and a Subcategory"/> <description value="Admin should be able to create a Root Category and a Subcategory"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml index b2e6119ef4e13..6096ee1fa3996 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml index 486fea9d91f9e..896a28d0298e6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateSimpleProductWithUnicodeTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index 71f2ffecb4652..7603400ba8dcd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminEditTextEditorProductAttributeTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml index 2edca19deeb00..8d5121cf21461 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassChangeProductsStatusTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassChangeProductsStatusTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml index 7c3e31e90c015..c0eebd1512d6d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassUpdateProductAttributesGlobalScopeTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml index be0bc2bf52a0b..fe0e46369c5e6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesMissingRequiredFieldTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassUpdateProductAttributesMissingRequiredFieldTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml index f7a04709b76d9..845c47c0e4c20 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassUpdateProductAttributesStoreViewScopeTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml index d29fde8590c9d..551b3437cb856 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml @@ -6,10 +6,11 @@ */ --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMoveAnchoredCategoryTest"> <annotations> <features value="Catalog"/> + <stories value="Edit categories"/> <title value="Admin should be able to move a category via categories tree and changes should be applied on frontend without a forced cache cleaning"/> <description value="Admin should be able to move a category via categories tree and changes should be applied on frontend without a forced cache cleaning"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml index 012c956c2dbef..264615ff6736f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMultipleWebsitesUseDefaultValuesTest"> <annotations> <features value="Catalog"/> + <stories value="Create websites"/> <title value="Use Default Value checkboxes should be checked for new website scope"/> <description value="Use Default Value checkboxes for product attribute should be checked for new website scope"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml index b079d35296e43..2884cb26cf813 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateAttributeTest.xml @@ -7,9 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminProductGridFilteringByDateAttributeTest"> <annotations> + <features value="Catalog"/> + <stories value="Filter products"/> <title value="Verify Set Product as new Filter input on Product Grid doesn't getreset to currentDate"/> <description value="Data input in the new from date filter field should not change"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml index c8be44eb73ca9..a882c6e7817ce 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductStatusAttributeDisabledByDefaultTest.xml @@ -7,9 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminProductStatusAttributeDisabledByDefaultTest"> <annotations> + <features value="Catalog"/> + <stories value="Create products"/> <title value="Verify the default option value for product Status attribute is set correctly during product creation"/> <description value="The default option value for product Status attribute is set correctly during product creation"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml index f20e6caf637d4..9760dc579b10b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageSimpleProductTest"> <annotations> <features value="Catalog"/> @@ -46,9 +46,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml index 86cc16e141e0a..a740b700c3026 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultImageVirtualProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageVirtualProductTest"> <annotations> <features value="Catalog"/> @@ -46,9 +46,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml new file mode 100644 index 0000000000000..1bd218d18c27d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to remove default product video from a Simple Product"/> + <description value="Admin should be able to remove default product video from a Simple Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-206"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="adminProductIndexPageAdd"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml new file mode 100644 index 0000000000000..e6d3978cad7bb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoVirtualProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to remove default product video from a Virtual Product"/> + <description value="Admin should be able to remove default product video from a Virtual Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-204"/> + <group value="Catalog"/> + </annotations> + + <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> + + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageFromCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageFromCategoryTest.xml index c68a848fb0fb6..fb33e18379982 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageFromCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageFromCategoryTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveImageFromCategoryTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml new file mode 100644 index 0000000000000..240a5492355cf --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRequiredFieldsHaveRequiredFieldIndicatorTest"> + <annotations> + <stories value="Verify the presence of required field indicators across different pages in Magento Admin"/> + <title value="Required fields should have the required asterisk indicator "/> + <description value="Verify that Required fields should have the required indicator icon next to the field name"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94330"/> + <group value="Catalog"/> + </annotations> + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToCategoryPage"/> + <waitForElementVisible selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="waitForAddSubCategoryVisible"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategory"/> + + <!-- Verify that the Category Name field has the required field name indicator --> + <executeJS function="{{AdminCategoryBasicFieldSection.RequiredFieldIndicator}}" stepKey="getRequiredFieldIndicator"/> + <assertEquals expected='"*"' expectedType="string" actualType="variable" actual="getRequiredFieldIndicator" message="pass" stepKey="assertRequiredFieldIndicator1"/> + + <executeJS function="{{AdminCategoryBasicFieldSection.RequiredFieldIndicatorColor}}" stepKey="getRequiredFieldIndicatorColor"/> + <assertEquals expected="rgb(226, 38, 38)" expectedType="string" actualType="variable" actual="getRequiredFieldIndicatorColor" message="pass" stepKey="assertRequiredFieldIndicator2"/> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndexPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="addProductDropdown"/> + <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="addSimpleProduct"/> + + <!-- Verify that the Product Name and Sku fields have the required field name indicator --> + <executeJS function="{{AdminProductFormSection.RequiredNameIndicator}}" stepKey="productNameRequiredFieldIndicator"/> + <assertEquals expected='"*"' expectedType="string" actualType="variable" actual="productNameRequiredFieldIndicator" message="pass" stepKey="assertRequiredFieldIndicator3"/> + <executeJS function="{{AdminProductFormSection.RequiredSkuIndicator}}" stepKey="productSkuRequiredFieldIndicator"/> + <assertEquals expected='"*"' expectedType="string" actualType="variable" actual="productSkuRequiredFieldIndicator" message="pass" stepKey="assertRequiredFieldIndicator4"/> + + <!-- Verify that the CMS page have the required field name indicator next to Page Title --> + <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnPagePagesGrid"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{CmsPagesPageActionsSection.addNewPageButton}}" stepKey="clickAddNewPage"/> + <executeJS function="{{CmsNewPagePageBasicFieldsSection.RequiredFieldIndicator}}" stepKey="pageTitleRequiredFieldIndicator"/> + <assertEquals expected='"*"' expectedType="string" actualType="variable" actual="pageTitleRequiredFieldIndicator" message="pass" stepKey="assertRequiredFieldIndicator5"/> + + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml new file mode 100644 index 0000000000000..bc5a0319bae7a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductEditUiTest.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSimpleProductUiValidationTest"> + <annotations> + <features value="Catalog"/> + <stories value="Edit products"/> + <title value="UI elements on the simple product edit screen should be organized as expected"/> + <description value="Admin should be able to use simple product UI in expected manner"/> + <testCaseId value="MAGETWO-92835"/> + <group value="Catalog"/> + <severity value="AVERAGE"/> + </annotations> + + <before> + <!-- This was copied and modified from the EndToEndB2CGuestUserTest --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <!--check admin for valid Enable Status label--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToEditPage"/> + <waitForPageLoad stepKey="wait1"/> + <seeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="seeCheckboxEnableProductIsChecked"/> + + <!--check click on wrapping container does not trigger status change--> + <click selector="{{AdminProductFormSection.enableProductAttributeLabelWrapper}}" stepKey="clickEnableProductWrapper"/> + <seeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="seeCheckboxEnableProductIsCheckedAfterWrapperClick"/> + + <!--check click on label itself does trigger status change--> + <click selector="{{AdminProductFormSection.enableProductAttributeLabel}}" stepKey="clickEnableProductLabel"/> + <dontSeeCheckboxIsChecked selector="{{AdminProductFormSection.productStatus}}" stepKey="dontSeeCheckboxEnableProductIsCheckedAfterLabelClick"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml index b51f6a6e43249..1cd0e15780c11 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminSimpleProductImagesTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml new file mode 100644 index 0000000000000..f5e5911352c86 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create/edit simple product"/> + <title value="Admin should be able to set/edit product Content when editing a simple product"/> + <description value="Admin should be able to set/edit product Content when editing a simple product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3422"/> + <group value="Catalog"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <!--Admin Login--> + <actionGroup stepKey="loginToAdminPanel" ref="LoginAsAdmin"/> + <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + </before> + <after> + <!-- Delete simple product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + <!--Admin Logout--> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + + <!--Add content--> + <!--A generic scroll scrolls past this element, in doing this it fails to execute certain actions on the element and others below it. By scrolling slightly above it it resolves this issue.--> + <scrollTo selector="{{AdminProductContentSection.sectionHeader}}" x="0" y="-100" stepKey="scrollTo"/> + <click selector="{{AdminProductContentSection.sectionHeader}}" stepKey="openDescriptionDropDown"/> + <fillField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="This is the long description" stepKey="fillLongDescription"/> + <fillField selector="{{AdminProductContentSection.shortDescriptionTextArea}}" userInput="This is the short description" stepKey="fillShortDescription"/> + + <!--save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown"/> + + <!--Edit content--> + <click selector="{{AdminProductContentSection.sectionHeader}}" stepKey="openDescriptionDropDownEdit"/> + <scrollTo selector="{{AdminProductContentSection.sectionHeader}}" stepKey="scrollToEdit"/> + <fillField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="EDIT ~ This is the long description ~ EDIT" stepKey="editLongDescription"/> + <fillField selector="{{AdminProductContentSection.shortDescriptionTextArea}}" userInput="EDIT ~ This is the short description ~ EDIT" stepKey="editShortDescription"/> + + <!--save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButtonAfterEdit"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShownAgain"/> + + <!--Checking content admin--> + <click selector="{{AdminProductContentSection.sectionHeader}}" stepKey="openDescriptionDropDownAgain"/> + <scrollTo selector="{{AdminProductContentSection.sectionHeader}}" stepKey="scrollToAgain"/> + <seeInField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="EDIT ~ This is the long description ~ EDIT" stepKey="seeLongDescriptionAdmin"/> + <seeInField selector="{{AdminProductContentSection.shortDescriptionTextArea}}" userInput="EDIT ~ This is the short description ~ EDIT" stepKey="seeShortDescriptionAdmin"/> + + <!--Checking content storefront--> + <amOnPage url="{{SimpleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <see selector="{{StorefrontProductInfoMainSection.productDescription}}" userInput="EDIT ~ This is the long description ~ EDIT" stepKey="seeLongDescriptionStorefront"/> + <see selector="{{StorefrontProductInfoMainSection.productShortDescription}}" userInput="EDIT ~ This is the short description ~ EDIT" stepKey="seeShortDescriptionStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml new file mode 100644 index 0000000000000..b06502ce94c65 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSimpleSetEditRelatedProductsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create/edit simple product"/> + <title value="Admin should be able to set/edit Related Products information when editing a simple product"/> + <description value="Admin should be able to set/edit Related Products information when editing a simple product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3411"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct0"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + </before> + <after> + <!-- Delete simple product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <deleteData createDataKey="simpleProduct0" stepKey="deleteSimpleProduct0"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + </after> + + <!--Create product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="SimpleProduct3"/> + </actionGroup> + + <!--Add related product--> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct0"> + <argument name="sku" value="$$simpleProduct0.sku$$"/> + </actionGroup> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown"/> + + <!--Add another related product--> + <actionGroup ref="addRelatedProductBySku" stepKey="addRelatedProduct1"> + <argument name="sku" value="$$simpleProduct1.sku$$"/> + </actionGroup> + + <!--Remove previous related product--> + <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.removeRelatedProduct($$simpleProduct0.sku$$)}}" stepKey="removeRelatedProduct"/> + + <!--Save the product--> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButtonAfterEdit"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShownAgain"/> + + <!--See related product in admin--> + <scrollTo selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" stepKey="scrollTo"/> + <conditionalClick selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDropdown}}" dependentSelector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedDependent}}" visible="false" stepKey="openDropDownIfClosedRelatedSee"/> + <see selector="{{AdminProductFormRelatedUpSellCrossSellSection.selectedRelatedProduct}}" userInput="$$simpleProduct1.sku$$" stepKey="seeRelatedProduct"/> + + <!--See related product in storefront--> + <amOnPage url="{{SimpleProduct3.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <seeElement selector="{{StorefrontProductRelatedProductsSection.relatedProductName($$simpleProduct1.sku$$)}}" stepKey="seeRelatedProductInStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index 1441e2175d08e..dc6cf012840eb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminUnassignProductAttributeFromAttributeSetTest"> <annotations> <features value="Catalog"/> @@ -17,8 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MC-194"/> <group value="Catalog"/> - <!-- Skip because of MAGETWO-92780 --> - <group value="skip"/> + <skip> + <issueId value="MAGETWO-92780"/> + </skip> </annotations> <before> <createData entity="productDropDownAttribute" stepKey="attribute"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml index 013b1b6d38123..2ff83afa15e5e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminUpdateCategoryStoreUrlKeyTest"> <annotations> <features value="SEO-friendly URL Key Update"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml new file mode 100644 index 0000000000000..c9932de808006 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminVirtualProductSetEditContentTest" extends="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create/edit virtual product"/> + <title value="Admin should be able to set/edit product Content when editing a virtual product"/> + <description value="Admin should be able to set/edit product Content when editing a virtual product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3425"/> + <group value="Catalog"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <after> + <!-- Delete virtual product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + + <!--Checking content storefront--> + <amOnPage url="{{defaultVirtualProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml new file mode 100644 index 0000000000000..e630545fff8fb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminVirtualSetEditRelatedProductsTest" extends="AdminSimpleSetEditRelatedProductsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create/edit virtual product"/> + <title value="Admin should be able to set/edit Related Products information when editing a virtual product"/> + <description value="Admin should be able to set/edit Related Products information when editing a virtual product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3415"/> + <group value="Catalog"/> + </annotations> + <before></before> + <after> + <!-- Delete virtual product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + + <!--See related product in storefront--> + <amOnPage url="{{defaultVirtualProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml index 03d919e329115..a4c8b492d9d84 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml new file mode 100644 index 0000000000000..0eb8f5668751a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchVirtualProductByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search virtual product with product name"/> + <description value="Guest customer should be able to advance search virtual product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-137"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiVirtualProductWithDescription" stepKey="product"/> + </before> + </test> + <test name="AdvanceCatalogSearchVirtualProductBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search virtual product with product sku"/> + <description value="Guest customer should be able to advance search virtual product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-162"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiVirtualProductWithDescription" stepKey="product"/> + </before> + </test> + <test name="AdvanceCatalogSearchVirtualProductByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search virtual product with product description"/> + <description value="Guest customer should be able to advance search virtual product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-163"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiVirtualProductWithDescription" stepKey="product"/> + </before> + </test> + <test name="AdvanceCatalogSearchVirtualProductByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search virtual product with product short description"/> + <description value="Guest customer should be able to advance search virtual product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-164"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiVirtualProductWithDescription" stepKey="product"/> + </before> + </test> + <test name="AdvanceCatalogSearchVirtualProductByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Catalog"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search virtual product with product price"/> + <description value="Guest customer should be able to advance search virtual product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-165"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiVirtualProductWithDescription" stepKey="product"/> + </before> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml index c97d2c45be951..899f3e61b5b86 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ConfigurableOptionTextinputLengthValidationHintTest"> <annotations> <features value="Product Customizable Option"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml index 4ec775f7ea2d8..5cae81b36a323 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteCategoriesTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="DeleteCategoriesTest"> <annotations> <features value="Catalog"/> + <stories value="Delete categories"/> <title value="Admin should be able to delete the default root category and subcategories and still see products in the storefront"/> <description value="Admin should be able to delete the default root category and subcategories and still see products in the storefront"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml index a9829b67189cb..1419ca4cb42ef 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <annotations> <features value="End to End scenarios"/> @@ -16,8 +16,9 @@ <description value="Admin creates products, creates and manages categories, creates promotions, creates an order, processes an order, processes a return, uses admin grids"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-87014"/> - <!-- Skipped, see: https://jira.corp.magento.com/browse/MQE-891 --> - <group value="skip"/> + <skip> + <issueId value="MQE-891"/> + </skip> </annotations> <after> <actionGroup ref="logout" stepKey="logoutOfAdmin"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index e80de8122e810..7c0de6da18caf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <annotations> <features value="End to End scenarios"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index b8827616e3ec5..3f48c3ca811e3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <before> <createData entity="ApiCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetSimpleProductTest.xml new file mode 100644 index 0000000000000..9ee56c02c7710 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetSimpleProductTest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetSimpleProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="Catalog"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Simple Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Simple Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-104"/> + <group value="Catalog"/> + <group value="WYSIWYGDisabled"/> + </annotations> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Create a Simple Product to appear in the widget --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductBtn}}" stepKey="clickAddProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{_defaultProduct.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="100" stepKey="fillProductQuantity"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{_defaultProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetVirtualProductTest.xml new file mode 100644 index 0000000000000..a4e0d8708eb49 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsListWidgetVirtualProductTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetVirtualProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="Catalog"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Virtual Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Virtual Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-122"/> + <group value="Catalog"/> + <group value="WYSIWYGDisabled"/> + + </annotations> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Create a Virtual Product to appear in the widget --> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="toggleAddProductButton"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickAddVirtualProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="123.45" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="100" stepKey="fillProductQuantity"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here, via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{_defaultProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index 5ed2bd5f75c01..e9e9eb0158789 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="SaveProductWithCustomOptionsAdditionalWebsiteTest"> <annotations> <features value="Save a product with Custom Options and assign to a different website"/> @@ -18,6 +18,7 @@ <testCaseId value="MAGETWO-91436"/> <group value="product"/> </annotations> + <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <!--Create new website --> <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createAdditionalWebsite"> @@ -34,7 +35,7 @@ <!--Create Store view --> <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> - <waitForElementVisible selector="{{AdminStoresMainActionsSection.createStoreViewButton}}" stepKey="waitForStoreViewBtn"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePage"/> <click selector="{{AdminStoresMainActionsSection.createStoreViewButton}}" stepKey="createStoreViewButton"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <selectOption userInput="Second Store" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreGroup"/> @@ -46,7 +47,7 @@ <conditionalClick selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" dependentSelector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" visible="true" stepKey="AcceptNewStoreViewCreation"/> <waitForElementVisible selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="waitForPageReolad"/> <see userInput="You saved the store view." stepKey="seeSaveMessage" /> - + </before> <after> <actionGroup ref="ResetWebUrlOptions" stepKey="resetUrlOption"/> <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteTestWebsite"> @@ -58,6 +59,7 @@ <actionGroup ref="EnableWebUrlOptions" stepKey="addStoreCodeToUrls"/> <!--Create a Simple Product with Custom Options --> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToCatalogProductGrid"/> + <waitForPageLoad stepKey="waitForCatalogProductGrid"/> <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductDropdown"/> <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="clickAddSimpleProduct"/> <fillField userInput="{{_defaultProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillName"/> @@ -65,8 +67,7 @@ <fillField userInput="{{_defaultProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillPrice"/> <fillField userInput="{{_defaultProduct.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" stepKey="fillQuantity"/> - <!--<click selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" stepKey="openCustomOptionsSection"/>--> - <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses2"/> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses2"/> <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> <waitForPageLoad stepKey="waitAfterAddOption"/> <fillField selector="input[name='product[options][0][title]']" userInput="Radio Option" stepKey="fillOptionTitle"/> @@ -98,7 +99,7 @@ <waitForLoadingMaskToDisappear stepKey="waitForProductPagetoSaveAgain"/> <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessageAgain"/> - <click selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" stepKey="openCustomOptionsSection2"/> + <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomOptionsSection2"/> <seeNumberOfElements selector=".admin__dynamic-rows[data-index='values'] tr.data-row" userInput="3" stepKey="see4RowsOfOptions"/> </test> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml index e05a259eaf974..0049dcb504335 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="SimpleProductTwoCustomOptionsTest"> <annotations> <features value="Catalog"/> @@ -41,7 +41,7 @@ </after> <!-- opens the custom option panel and clicks add options --> - <click stepKey="openCustomizableOptions" selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}"/> + <click stepKey="openCustomizableOptions" selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}"/> <waitForPageLoad stepKey="waitForCustomOptionsOpen"/> <!-- Create a custom option with 2 values --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml index 956677c8b5de5..569eb290bae3c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml @@ -7,14 +7,20 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontProductNameWithDoubleQuote"> <annotations> + <features value="Catalog"/> + <stories value="Create products"/> <title value="Product with double quote in name"/> <description value="Product with a double quote in the name should appear correctly on the storefront"/> <severity value="CRITICAL"/> <group value="product"/> <testCaseId value="MAGETWO-92384"/> + <group value="skip"/> + <skip> + <issueId value="MAGETWO-93261"/> + </skip> </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> @@ -60,4 +66,63 @@ <argument name="product" value="SimpleProductNameWithDoubleQuote"/> </actionGroup> </test> + + <test name="StorefrontProductNameWithHTMLEntities"> + <annotations> + <features value="Catalog"/> + <stories value="Create product"/> + <title value=":Proudct with html special characters in name"/> + <description value="Product with html entities in the name should appear correctly on the PDP breadcrumbs on storefront"/> + <severity value="CRITICAL"/> + <group value="product"/> + <testCaseId value="MAGETWO-93794"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategoryOne"/> + <createData entity="productWithHTMLEntityOne" stepKey="productOne"> + <requiredEntity createDataKey="createCategoryOne"/> + </createData> + <createData entity="productWithHTMLEntityTwo" stepKey="productTwo"> + <requiredEntity createDataKey="createCategoryOne"/> + </createData> + </before> + <after> + <deleteData createDataKey="productOne" stepKey="deleteProductOne"/> + <deleteData createDataKey="productTwo" stepKey="deleteProductTwo"/> + <deleteData createDataKey="createCategoryOne" stepKey="deleteCategory"/> + </after> + + <!--Check product in category listing--> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategoryOne.name$$)}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad stepKey="waitforCategoryPageToLoad"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" userInput="{{productWithHTMLEntityOne.name}}" stepKey="seeCorrectNameProd1CategoryPage"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityTwo.name)}}" userInput="{{productWithHTMLEntityTwo.name}}" stepKey="seeCorrectNameProd2CategoryPage"/> + + <!--Open product display page--> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" stepKey="clickProductToGoProductPage"/> + <waitForPageLoad stepKey="waitForProductDisplayPageLoad2"/> + + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{productWithHTMLEntityOne.name}}" stepKey="seeCorrectName"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{productWithHTMLEntityOne.sku}}" stepKey="seeCorrectSku"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="${{productWithHTMLEntityOne.price}}" stepKey="seeCorrectPrice"/> + + <!--Veriy the breadcrumbs on Product Display page--> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="Home" stepKey="seeHomePageInBreadcrumbs1"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$createCategoryOne.name$$" stepKey="seeCorrectBreadCrumbCategory"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$productOne.name$$" stepKey="seeCorrectBreadCrumbProduct"/> + + <click selector="{{StorefrontNavigationSection.topCategory($$createCategoryOne.name$$)}}" stepKey="goBackToCategoryPage"/> + <waitForPageLoad stepKey="waitforCategoryPageToLoad2"/> + + <!--Open product display page--> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByNumber('1')}}" stepKey="goToProduct2DisplayPage"/> + <!--<click selector="{{StorefrontCategoryProductSection.ProductTitleByName(productWithHTMLEntityOne.name)}}" stepKey="clickProductToGoProductPage"/>--> + <waitForPageLoad stepKey="waitForProductDisplayPageLoad3"/> + + <!--Veriy the breadcrumbs on Product Display page--> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="Home" stepKey="seeHomePageInBreadcrumbs2"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$createCategoryOne.name$$" stepKey="seeCorrectBreadCrumbCategory2"/> + <see selector="{{StorefrontNavigationSection.breadcrumbs}}" userInput="$$productTwo.name$$" stepKey="seeCorrectBreadCrumbProduct2"/> + + </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml index 92013f6f9d0f0..1c1b47a6bded9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml @@ -7,9 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontProductWithEmptyAttributeTest"> <annotations> + <features value="Catalog"/> + <stories value="Create products"/> <title value="Product attribute is not visible on storefront if it is empty"/> <description value="Product attribute should not be visible on storefront if it is empty"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml index c03241348e807..d7f98c4cdd307 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml @@ -7,9 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontProductsCompareWithEmptyAttributeTest"> <annotations> + <features value="Catalog"/> + <stories value="Product attributes"/> <title value="Product attribute is not visible on product compare page if it is empty"/> <description value="Product attribute should not be visible on the product compare page if it is empty for all products that are being compared, not even displayed as N/A"/> <severity value="MAJOR"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml index f5c71f95281ce..1df6ae654001f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest"> <annotations> <features value="Catalog"/> + <stories value="Custom options different storeviews"/> <title value="Admin should be able to sell products with different variants of their own"/> <description value="Admin should be able to sell products with different variants of their own"/> <severity value="CRITICAL"/> @@ -77,7 +78,7 @@ <!-- Update Product with Option Value DropDown 1--> - <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses2"/> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses2"/> <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="checkAddOption1"/> <waitForPageLoad time="10" stepKey="waitForPageLoad7"/> <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionTitle('New Option')}}" userInput="Custom Options 1" stepKey="fillOptionTitle1"/> @@ -104,7 +105,7 @@ <!-- Open tab Customizable Options --> <waitForPageLoad time="10" stepKey="waitForPageLoad2"/> - <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses3"/> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses3"/> <!-- Update Option Customizable Options and Option Value 1--> @@ -266,7 +267,7 @@ <!-- Open tab Customizable Options --> <waitForPageLoad time="30" stepKey="waitForPageLoad9"/> - <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customezableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses4" /> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="clickIfContentTabCloses4" /> <!-- Update Option Customizable Options and Option Value 1--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml index 3a7feb34354d7..6b444f1f6663b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontPurchaseProductWithCustomOptions"> <annotations> <features value="Catalog"/> @@ -16,8 +16,7 @@ <description value="Admin should be able to sell products with different variants of their own"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-61717"/> - <!--Skip because of issue MAGETWO-90719--> - <group value="skip"/> + <group value="Catalog"/> </annotations> <before> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> @@ -96,6 +95,7 @@ <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionField.title}}" stepKey="seeProductOptionFieldInput1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionArea.title}}" stepKey="seeProductOptionAreaInput1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{productWithOptions.file}}" stepKey="seeProductOptionFileInput1"/> + <seeElement selector="{{CheckoutPaymentSection.ProductOptionLinkActiveByProductItemName($createProduct.name$, productWithOptions.file)}}" stepKey="seeProductOptionFileInputLink1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueDropdown1.title}}" stepKey="seeProductOptionValueDropdown1Input1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeProductOptionValueRadioButtons1Input1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeProductOptionValueCheckboxInput1" /> @@ -129,6 +129,7 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionField.title}}" stepKey="seeAdminOrderProductOptionField" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionArea.title}}" stepKey="seeAdminOrderProductOptionArea"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{productWithOptions.file}}" stepKey="seeAdminOrderProductOptionFile"/> + <seeElement selector="{{AdminOrderItemsOrderedSection.productNameOptionsLink(productWithOptions.file)}}" stepKey="seeAdminOrderProductOptionFileLink"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueDropdown1.title}}" stepKey="seeAdminOrderProductOptionValueDropdown1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox" /> @@ -145,6 +146,7 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionField.title}}" stepKey="seeAdminOrderProductOptionField1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionArea.title}}" stepKey="seeAdminOrderProductOptionArea1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{productWithOptions.file}}" stepKey="seeAdminOrderProductOptionFile1"/> + <seeElement selector="{{AdminOrderItemsOrderedSection.productNameOptionsLink(productWithOptions.file)}}" stepKey="seeAdminOrderProductOptionFileLink1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueDropdown1.title}}" stepKey="seeAdminOrderProductOptionValueDropdown11"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton11"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox1" /> @@ -162,6 +164,7 @@ <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionField.title, ProductOptionField.title)}}" userInput="{{ProductOptionField.title}}" stepKey="seeStorefontOrderProductOptionField1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionArea.title, ProductOptionArea.title)}}" userInput="{{ProductOptionArea.title}}" stepKey="seeStorefontOrderProductOptionArea1"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptionsFile($createProduct.name$, ProductOptionFile.title, productWithOptions.file)}}" userInput="{{productWithOptions.file}}" stepKey="seeStorefontOrderProductOptionFile1"/> + <seeElement selector="{{StorefrontCustomerOrderSection.productCustomOptionsLink($createProduct.name$, ProductOptionFile.title, productWithOptions.file)}}" stepKey="seeStorefontOrderProductOptionFileLink1"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDropDown.title, ProductOptionValueDropdown1.title)}}" userInput="{{ProductOptionValueDropdown1.title}}" stepKey="seeStorefontOrderProductOptionValueDropdown11"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionRadiobutton.title, ProductOptionValueRadioButtons1.title)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeStorefontOrderProductOptionValueRadioButtons11"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionCheckbox.title, ProductOptionValueCheckbox.title)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeStorefontOrderProductOptionValueCheckbox1" /> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml new file mode 100644 index 0000000000000..9d7c616238451 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle"> + <annotations> + <features value="Catalog"/> + <stories value="Custom options"/> + <group value="Catalog"/> + <title value="Admin should be able to see the full title of the selected custom option value in the order"/> + <description value="Admin should be able to see the full title of the selected custom option value in the order"/> + <severity value="MAJOR"/> + <testCaseId value="MC-3043"/> + <group value="skip"/> + <!-- Skip due to MQE-1128 --> + <skip> + <issueId value="MQE-1128"/> + </skip> + </annotations> + <before> + <!--Create Simple Product with Custom Options--> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">17</field> + </createData> + <updateData createDataKey="createProduct" entity="productWithOptions2" stepKey="updateProductWithOptions"/> + + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <!-- Login Customer Storefront --> + + <amOnPage url="{{StorefrontCustomerSignInPage.url}}" stepKey="amOnSignInPage"/> + <fillField userInput="$$createCustomer.email$$" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> + <fillField userInput="$$createCustomer.password$$" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> + <click selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" stepKey="clickSignInAccountButton"/> + + <!-- Checking the correctness of displayed prices for user parameters --> + + <amOnPage url="{{StorefrontHomePage.url}}$$createProduct.custom_attributes[url_key]$$.html" stepKey="amOnProductPage"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptionsDropDown(ProductOptionDropDownWithLongValuesTitle.title, ProductOptionValueDropdownLongTitle1.price)}}" stepKey="checkDropDownProductOption"/> + + <!-- Adding items to the checkout --> + + <selectOption userInput="{{ProductOptionValueDropdownLongTitle1.price}}" selector="{{StorefrontProductInfoMainSection.productOptionSelect(ProductOptionDropDownWithLongValuesTitle.title)}}" stepKey="seeProductOptionDropDown"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="finalProductPrice"/> + + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!-- Checking the correctness of displayed custom options for user parameters on checkout --> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart" /> + + <conditionalClick selector="{{CheckoutPaymentSection.cartItemsArea}}" dependentSelector="{{CheckoutPaymentSection.cartItemsArea}}" visible="true" stepKey="exposeMiniCart"/> + + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskForCartItem"/> + <waitForElement selector="{{CheckoutPaymentSection.cartItemsAreaActive}}" time="30" stepKey="waitForCartItemsAreaActive"/> + + <see selector="{{CheckoutPaymentSection.cartItems}}" userInput="$$createProduct.name$$" stepKey="seeProductInCart"/> + + <conditionalClick selector="{{CheckoutPaymentSection.ProductOptionsByProductItemName($$createProduct.name$$)}}" dependentSelector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($$createProduct.name$$)}}" visible="false" stepKey="exposeProductOptions"/> + + <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($$createProduct.name$$)}}" userInput="{{ProductOptionValueDropdownLongTitle1.title}}" stepKey="seeProductOptionValueDropdown1Input1"/> + + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + + <!-- Place Order --> + + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <!-- Login to Admin and open Order --> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnOrdersPage"/> + <fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> + <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + + <!-- Checking the correctness of displayed custom options for user parameters on Order --> + + <dontSee selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueDropdownLongTitle1.title}}" stepKey="dontSeeAdminOrderProductOptionValueDropdown1"/> + <grabTextFrom selector="{{AdminOrderItemsOrderedSection.productNameOptions}} dd" stepKey="productOptionValueText"/> + <assertEquals stepKey="checkProductOptionValue"> + <actualResult type="variable">productOptionValueText</actualResult> + <expectedResult type="string">Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj111 ...</expectedResult> + </assertEquals> + <moveMouseOver selector="{{AdminOrderItemsOrderedSection.productNameOptions}} dd" stepKey="hoverProduct"/> + <waitForElementVisible selector="{{AdminOrderItemsOrderedSection.productNameOptions}} dd:nth-child(2)" stepKey="waitForCustomOptionValueFullName"/> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueDropdownLongTitle1.title}}" stepKey="seeAdminOrderProductOptionValueDropdown1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyChildCategoriesShouldNotIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyChildCategoriesShouldNotIncludeInMenuTest.xml index 51290ba12f2c7..455e9b58156eb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyChildCategoriesShouldNotIncludeInMenuTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyChildCategoriesShouldNotIncludeInMenuTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyChildCategoriesShouldNotIncludeInMenuTest"> <annotations> <features value="Catalog"/> + <stories value="Create categories"/> <title value="Customer should not see categories that are not included in the menu"/> <description value="Customer should not see categories that are not included in the menu"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml index 234148830bd43..53bcac5b1d5f0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyDefaultWYSIWYGToolbarOnProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml index 5fc0b6478ffb8..aad83cd0c2aff 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml index e2077f8676706..26dd94b19de64 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/composer.json b/app/code/Magento/Catalog/Test/Mftf/composer.json deleted file mode 100644 index 501fc0fa6c8db..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/composer.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-indexer": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-product-alert": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/DeleteTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/DeleteTest.php index af1ded6987196..196b4df5b47c0 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/DeleteTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/DeleteTest.php @@ -71,7 +71,7 @@ protected function setUp() false, true, true, - ['addSuccess'] + ['addSuccessMessage'] ); $this->categoryRepository = $this->createMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class); $context->expects($this->any()) diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php index f6a586950afdc..74173dc926d97 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Category; -use Magento\Catalog\Controller\Adminhtml\Category\Save as Model; - /** * Class SaveTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -104,7 +102,7 @@ protected function setUp() false, true, true, - ['addSuccess', 'getMessages'] + ['addSuccessMessage', 'getMessages'] ); $this->save = $this->objectManager->getObject( @@ -394,7 +392,7 @@ public function testExecute($categoryId, $storeId, $parentId) $categoryMock->expects($this->once()) ->method('save'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('You saved the category.')); $categoryMock->expects($this->at(1)) ->method('getId') @@ -463,9 +461,25 @@ public function dataProviderExecute() */ public function imagePreprocessingDataProvider() { + $dataWithImage = [ + 'image' => 'path.jpg', + 'name' => 'category', + 'description' => '', + 'parent' => 0 + ]; + $expectedSameAsDataWithImage = $dataWithImage; + + $dataWithoutImage = [ + 'name' => 'category', + 'description' => '', + 'parent' => 0 + ]; + $expectedIfDataWithoutImage = $dataWithoutImage; + $expectedIfDataWithoutImage['image'] = ''; + return [ - [['attribute1' => null, 'attribute2' => 123]], - [['attribute2' => 123]] + 'categoryPostData contains image' => [$dataWithImage, $expectedSameAsDataWithImage], + 'categoryPostData doesn\'t contain image' => [$dataWithoutImage, $expectedIfDataWithoutImage], ]; } @@ -473,8 +487,9 @@ public function imagePreprocessingDataProvider() * @dataProvider imagePreprocessingDataProvider * * @param array $data + * @param array $expected */ - public function testImagePreprocessingWithoutValue($data) + public function testImagePreprocessing($data, $expected) { $eavConfig = $this->createPartialMock(\Magento\Eav\Model\Config::class, ['getEntityType']); @@ -484,49 +499,17 @@ public function testImagePreprocessingWithoutValue($data) $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ new \Magento\Framework\DataObject([ - 'attribute_code' => 'attribute1', + 'attribute_code' => 'image', 'backend' => $imageBackendModel ]), new \Magento\Framework\DataObject([ - 'attribute_code' => 'attribute2', + 'attribute_code' => 'name', 'backend' => new \Magento\Framework\DataObject() - ]) - ]]); - - $eavConfig->expects($this->once()) - ->method('getEntityType') - ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) - ->will($this->returnValue($collection)); - - $model = $this->objectManager->getObject(\Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ - 'eavConfig' => $eavConfig - ]); - - $result = $model->imagePreprocessing($data); - - $this->assertEquals([ - 'attribute1' => false, - 'attribute2' => 123 - ], $result); - } - - public function testImagePreprocessingWithValue() - { - $eavConfig = $this->createPartialMock(\Magento\Eav\Model\Config::class, ['getEntityType']); - - $imageBackendModel = $this->objectManager->getObject( - \Magento\Catalog\Model\Category\Attribute\Backend\Image::class - ); - - $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ - new \Magento\Framework\DataObject([ - 'attribute_code' => 'attribute1', - 'backend' => $imageBackendModel ]), new \Magento\Framework\DataObject([ - 'attribute_code' => 'attribute2', + 'attribute_code' => 'level', 'backend' => new \Magento\Framework\DataObject() - ]) + ]), ]]); $eavConfig->expects($this->once()) @@ -534,18 +517,12 @@ public function testImagePreprocessingWithValue() ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) ->will($this->returnValue($collection)); - $model = $this->objectManager->getObject(Model::class, [ + $model = $this->objectManager->getObject(\Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ 'eavConfig' => $eavConfig ]); - $result = $model->imagePreprocessing([ - 'attribute1' => 'somevalue', - 'attribute2' => null - ]); + $result = $model->imagePreprocessing($data); - $this->assertEquals([ - 'attribute1' => 'somevalue', - 'attribute2' => null - ], $result); + $this->assertEquals($expected, $result); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php index 5a977b7934670..0ddd89afeac22 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/EditTest.php @@ -94,7 +94,7 @@ private function prepareContext() $messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) ->setMethods([]) ->disableOriginalConstructor()->getMock(); - $messageManager->expects($this->any())->method('addError')->willReturn(true); + $messageManager->expects($this->any())->method('addErrorMessage')->willReturn(true); $this->context = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class) ->setMethods(['getRequest', 'getObjectManager', 'getMessageManager', 'getResultRedirectFactory']) ->disableOriginalConstructor()->getMock(); diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php index c88a008efb19b..de44af7f58afc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php @@ -250,8 +250,8 @@ public function testExecuteThatProductIdsAreObtainedFromAttributeHelper() ['inventory', [], [7]], ])); - $this->messageManager->expects($this->never())->method('addError'); - $this->messageManager->expects($this->never())->method('addException'); + $this->messageManager->expects($this->never())->method('addErrorMessage'); + $this->messageManager->expects($this->never())->method('addExceptionMessage'); $this->object->execute(); } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php index dce3f5886d1a8..ff44a91a64998 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php @@ -95,6 +95,9 @@ class HelperTest extends \PHPUnit\Framework\TestCase */ protected $attributeFilterMock; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = new ObjectManager($this); @@ -198,10 +201,12 @@ public function testInitialize( 'option2' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '13'], 'option3' => ['is_delete' => false, 'name' => 'name1', 'price' => 'price1', 'option_id' => '14'] ]; + $specialFromDate = '2018-03-03 19:30:00'; $productData = [ 'stock_data' => ['stock_data'], 'options' => $optionsData, - 'website_ids' => $websiteIds + 'website_ids' => $websiteIds, + 'special_from_date' => $specialFromDate, ]; if (!empty($tierPrice)) { $productData = array_merge($productData, ['tier_price' => $tierPrice]); @@ -306,6 +311,7 @@ public function testInitialize( } $this->assertEquals($expectedLinks, $resultLinks); + $this->assertEquals($specialFromDate, $productData['special_from_date']); } /** diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/ShowUpdateResultTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/ShowUpdateResultTest.php index ba716fdb53c89..47a60a1916142 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/ShowUpdateResultTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/ShowUpdateResultTest.php @@ -58,7 +58,7 @@ protected function getContext() $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); $objectManagerMock->expects($this->any()) ->method('get') - ->willreturn($productActionMock); + ->willReturn($productActionMock); $eventManager = $this->getMockBuilder(\Magento\Framework\Event\Manager::class) ->setMethods(['dispatch']) diff --git a/app/code/Magento/Catalog/Test/Unit/Cron/DeleteAbandonedStoreFlatTablesTest.php b/app/code/Magento/Catalog/Test/Unit/Cron/DeleteAbandonedStoreFlatTablesTest.php new file mode 100644 index 0000000000000..1a9d7959dda9c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Cron/DeleteAbandonedStoreFlatTablesTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Cron; + +use Magento\Catalog\Cron\DeleteAbandonedStoreFlatTables; +use Magento\Catalog\Helper\Product\Flat\Indexer; + +/** + * @covers \Magento\Catalog\Cron\DeleteAbandonedStoreFlatTables + */ +class DeleteAbandonedStoreFlatTablesTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var DeleteAbandonedStoreFlatTables + */ + private $deleteAbandonedStoreFlatTables; + + /** + * @var Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->indexerMock = $this->createMock(Indexer::class); + $this->deleteAbandonedStoreFlatTables = new DeleteAbandonedStoreFlatTables($this->indexerMock); + } + + /** + * Test execute method + * + * @return void + */ + public function testExecute() + { + $this->indexerMock->expects($this->once())->method('deleteAbandonedStoreFlatTables'); + $this->deleteAbandonedStoreFlatTables->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Cron/DeleteOutdatedPriceValuesTest.php b/app/code/Magento/Catalog/Test/Unit/Cron/DeleteOutdatedPriceValuesTest.php new file mode 100644 index 0000000000000..f1d0034c29580 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Cron/DeleteOutdatedPriceValuesTest.php @@ -0,0 +1,139 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Cron; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Cron\DeleteOutdatedPriceValues; +use Magento\Eav\Api\AttributeRepositoryInterface as AttributeRepository; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Eav\Model\Entity\Attribute\Backend\BackendInterface; +use Magento\Framework\App\Config\MutableScopeConfigInterface as ScopeConfig; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Store\Model\Store; + +/** + * @covers \Magento\Catalog\Cron\DeleteOutdatedPriceValues + */ +class DeleteOutdatedPriceValuesTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var DeleteOutdatedPriceValues + */ + private $deleteOutdatedPriceValues; + + /** + * @var AttributeRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeRepositoryMock; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceConnectionMock; + + /** + * @var ScopeConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var Attribute|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeMock; + + /** + * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $dbAdapterMock; + + /** + * @var BackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeBackendMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->resourceConnectionMock = $this->createMock(ResourceConnection::class); + $this->attributeRepositoryMock = $this->createMock(AttributeRepository::class); + $this->attributeMock = $this->createMock(Attribute::class); + $this->scopeConfigMock = $this->createMock(ScopeConfig::class); + $this->dbAdapterMock = $this->createMock(AdapterInterface::class); + $this->attributeBackendMock = $this->createMock(BackendInterface::class); + $this->deleteOutdatedPriceValues = new DeleteOutdatedPriceValues( + $this->resourceConnectionMock, + $this->attributeRepositoryMock, + $this->scopeConfigMock + ); + } + + /** + * Test execute method + * + * @return void + */ + public function testExecute() + { + $table = 'catalog_product_entity_decimal'; + $attributeId = 15; + $conditions = ['first', 'second']; + + $this->scopeConfigMock + ->expects($this->once()) + ->method('getValue') + ->with(Store::XML_PATH_PRICE_SCOPE) + ->willReturn(Store::XML_PATH_PRICE_SCOPE); + $this->attributeRepositoryMock + ->expects($this->once()) + ->method('get') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, ProductAttributeInterface::CODE_PRICE) + ->willReturn($this->attributeMock); + $this->attributeMock->expects($this->once())->method('getId')->willReturn($attributeId); + $this->attributeMock->expects($this->once())->method('getBackend')->willReturn($this->attributeBackendMock); + $this->attributeBackendMock->expects($this->once())->method('getTable')->willReturn($table); + $this->resourceConnectionMock->expects($this->once()) + ->method('getConnection') + ->willReturn($this->dbAdapterMock); + $this->dbAdapterMock->expects($this->exactly(2))->method('quoteInto')->willReturnMap([ + ['attribute_id = ?', $attributeId, null, null, $conditions[0]], + ['store_id != ?', Store::DEFAULT_STORE_ID, null, null, $conditions[1]], + ]); + $this->dbAdapterMock->expects($this->once())->method('delete')->with($table, $conditions); + $this->deleteOutdatedPriceValues->execute(); + } + + /** + * Test execute method + * The price scope config option is not equal to global value + * + * @return void + */ + public function testExecutePriceConfigIsNotSetToGlobal() + { + $this->scopeConfigMock + ->expects($this->once()) + ->method('getValue') + ->with(Store::XML_PATH_PRICE_SCOPE) + ->willReturn(null); + $this->attributeRepositoryMock + ->expects($this->never()) + ->method('get'); + $this->dbAdapterMock + ->expects($this->never()) + ->method('delete'); + + $this->deleteOutdatedPriceValues->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php new file mode 100644 index 0000000000000..e30ddda0b70b9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/CustomerData/CompareProductsTest.php @@ -0,0 +1,286 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\CustomerData; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\CustomerData\CompareProducts; +use Magento\Catalog\Helper\Output; +use Magento\Catalog\Helper\Product\Compare; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Url; +use Magento\Catalog\Model\ResourceModel\Product\Compare\Item\Collection; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class CompareProductsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CompareProducts + */ + private $model; + + /** + * @var Compare|\PHPUnit_Framework_MockObject_MockObject + */ + private $helperMock; + + /** + * @var Url|\PHPUnit_Framework_MockObject_MockObject + */ + private $productUrlMock; + + /** + * @var Output|\PHPUnit_Framework_MockObject_MockObject + */ + private $outputHelperMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManagerHelper; + + /** + * @var array + */ + private $productValueMap = [ + 'id' => 'getId', + ProductInterface::NAME => 'getName' + ]; + + protected function setUp() + { + parent::setUp(); + + $this->helperMock = $this->getMockBuilder(Compare::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productUrlMock = $this->getMockBuilder(Url::class) + ->disableOriginalConstructor() + ->getMock(); + $this->outputHelperMock = $this->getMockBuilder(Output::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->model = $this->objectManagerHelper->getObject( + CompareProducts::class, + [ + 'helper' => $this->helperMock, + 'productUrl' => $this->productUrlMock, + 'outputHelper' => $this->outputHelperMock + ] + ); + } + + /** + * Prepare compare items collection. + * + * @param array $items + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getItemCollectionMock(array $items) : \PHPUnit_Framework_MockObject_MockObject + { + $itemCollectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + + $itemCollectionMock->expects($this->any()) + ->method('getIterator') + ->willReturn(new \ArrayIterator($items)); + + return $itemCollectionMock; + } + + /** + * Prepare product mocks objects and add corresponding method mocks for helpers. + * + * @param array $dataSet + * @return array + */ + private function prepareProductsWithCorrespondingMocks(array $dataSet) : array + { + $items = []; + $urlMap = []; + $outputMap = []; + $helperMap = []; + + $count = count($dataSet); + + foreach ($dataSet as $data) { + $item = $this->getProductMock($data); + $items[] = $item; + + $outputMap[] = [$item, $data['name'], 'name', 'productName#' . $data['id']]; + $helperMap[] = [$item, 'http://remove.url/' . $data['id']]; + $urlMap[] = [$item, [], 'http://product.url/' . $data['id']]; + } + + $this->productUrlMock->expects($this->exactly($count)) + ->method('getUrl') + ->will($this->returnValueMap($urlMap)); + + $this->outputHelperMock->expects($this->exactly($count)) + ->method('productAttribute') + ->will($this->returnValueMap($outputMap)); + + $this->helperMock->expects($this->exactly($count)) + ->method('getPostDataRemove') + ->will($this->returnValueMap($helperMap)); + + return $items; + } + + /** + * Prepare mock of product object. + * + * @param array $data + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getProductMock(array $data) : \PHPUnit_Framework_MockObject_MockObject + { + $product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + + foreach ($data as $index => $value) { + $product->expects($this->once()) + ->method($this->productValueMap[$index]) + ->willReturn($value); + } + + return $product; + } + + public function testGetSectionData() + { + $dataSet = [ + ['id' => 1, 'name' => 'product#1'], + ['id' => 2, 'name' => 'product#2'], + ['id' => 3, 'name' => 'product#3'] + ]; + + $count = count($dataSet); + + $this->helperMock->expects($this->once()) + ->method('getItemCount') + ->willReturn($count); + + $items = $this->prepareProductsWithCorrespondingMocks($dataSet); + + $itemCollectionMock = $this->getItemCollectionMock($items); + + $this->helperMock->expects($this->once()) + ->method('getItemCollection') + ->willReturn($itemCollectionMock); + + $this->helperMock->expects($this->once()) + ->method('getListUrl') + ->willReturn('http://list.url'); + + $this->assertEquals( + [ + 'count' => $count, + 'countCaption' => __('%1 items', $count), + 'listUrl' => 'http://list.url', + 'items' => [ + [ + 'id' => 1, + 'product_url' => 'http://product.url/1', + 'name' => 'productName#1', + 'remove_url' => 'http://remove.url/1' + ], + [ + 'id' => 2, + 'product_url' => 'http://product.url/2', + 'name' => 'productName#2', + 'remove_url' => 'http://remove.url/2' + ], + [ + 'id' => 3, + 'product_url' => 'http://product.url/3', + 'name' => 'productName#3', + 'remove_url' => 'http://remove.url/3' + ] + ] + ], + $this->model->getSectionData() + ); + } + + public function testGetSectionDataNoItems() + { + $count = 0; + + $this->helperMock->expects($this->once()) + ->method('getItemCount') + ->willReturn($count); + + $this->helperMock->expects($this->never()) + ->method('getItemCollection'); + + $this->helperMock->expects($this->once()) + ->method('getListUrl') + ->willReturn('http://list.url'); + + $this->assertEquals( + [ + 'count' => $count, + 'countCaption' => __('%1 items', $count), + 'listUrl' => 'http://list.url', + 'items' => [] + ], + $this->model->getSectionData() + ); + } + + public function testGetSectionDataSingleItem() + { + $count = 1; + + $this->helperMock->expects($this->once()) + ->method('getItemCount') + ->willReturn($count); + + $items = $this->prepareProductsWithCorrespondingMocks( + [ + [ + 'id' => 12345, + 'name' => 'SingleProduct' + ] + ] + ); + + $itemCollectionMock = $this->getItemCollectionMock($items); + + $this->helperMock->expects($this->once()) + ->method('getItemCollection') + ->willReturn($itemCollectionMock); + + $this->helperMock->expects($this->once()) + ->method('getListUrl') + ->willReturn('http://list.url'); + + $this->assertEquals( + [ + 'count' => 1, + 'countCaption' => __('1 item'), + 'listUrl' => 'http://list.url', + 'items' => [ + [ + 'id' => 12345, + 'product_url' => 'http://product.url/12345', + 'name' => 'productName#12345', + 'remove_url' => 'http://remove.url/12345' + ] + ] + ], + $this->model->getSectionData() + ); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilterTest.php index 942a87ce3414f..157c72fcedf10 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilterTest.php @@ -66,7 +66,7 @@ public function testApplyWithoutCondition() $collectionMock->expects($this->once()) ->method('addCategoriesFilter') - ->with(['eq' => ['value']]); + ->with(['in' => ['value']]); $this->assertTrue($this->model->apply($filterMock, $collectionMock)); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php index 8d63530b91287..f1672d842de4e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php @@ -34,6 +34,9 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ private $filesystem; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -98,7 +101,7 @@ public function testBeforeSaveValueDeletion($value) $model->beforeSave($object); - $this->assertEquals('', $object->getTestAttribute()); + $this->assertEquals(null, $object->getTestAttribute()); } /** @@ -138,6 +141,9 @@ public function testBeforeSaveValueInvalid($value) $this->assertEquals('', $object->getTestAttribute()); } + /** + * Test beforeSaveAttributeFileName. + */ public function testBeforeSaveAttributeFileName() { $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); @@ -154,6 +160,9 @@ public function testBeforeSaveAttributeFileName() $this->assertEquals('test123.jpg', $object->getTestAttribute()); } + /** + * Test beforeSaveAttributeFileNameOutsideOfCategoryDir. + */ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() { $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [ @@ -186,6 +195,9 @@ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() ); } + /** + * Test beforeSaveTemporaryAttribute. + */ public function testBeforeSaveTemporaryAttribute() { $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); @@ -204,6 +216,9 @@ public function testBeforeSaveTemporaryAttribute() ], $object->getData('_additional_data_test_attribute')); } + /** + * Test beforeSaveAttributeStringValue. + */ public function testBeforeSaveAttributeStringValue() { $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); @@ -304,6 +319,9 @@ public function testAfterSaveWithoutAdditionalData($value) $model->afterSave($object); } + /** + * Test afterSaveWithExceptions. + */ public function testAfterSaveWithExceptions() { $model = $this->setUpModelForAfterSave(); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/AbstractActionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/AbstractActionTest.php index 9d58822fb6073..6ad14af44304e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/AbstractActionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/AbstractActionTest.php @@ -22,6 +22,14 @@ class AbstractActionTest extends \PHPUnit\Framework\TestCase */ protected $_eavSourceFactoryMock; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + + /** + * @return void + */ protected function setUp() { $this->_eavDecimalFactoryMock = $this->createPartialMock( @@ -32,12 +40,22 @@ protected function setUp() \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory::class, ['create'] ); + $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); $this->_model = $this->getMockForAbstractClass( \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction::class, - [$this->_eavDecimalFactoryMock, $this->_eavSourceFactoryMock, []] + [ + $this->_eavDecimalFactoryMock, + $this->_eavSourceFactoryMock, + $this->scopeConfig + ] ); } + /** + * @return void + */ public function testGetIndexers() { $expectedIndexers = [ @@ -73,6 +91,10 @@ public function testGetIndexerWithUnknownTypeThrowsException() $this->_model->getIndexer('unknown_type'); } + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testGetIndexer() { $this->_eavSourceFactoryMock->expects($this->once()) @@ -86,6 +108,10 @@ public function testGetIndexer() $this->assertEquals('source_return_value', $this->_model->getIndexer('source')); } + /** + * @return void + * @throws \Exception + */ public function testReindexWithoutArgumentsExecutesReindexAll() { $eavSource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source::class) @@ -110,6 +136,10 @@ public function testReindexWithoutArgumentsExecutesReindexAll() ->method('create') ->will($this->returnValue($eavDecimal)); + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->willReturn(1); + $this->_model->reindex(); } @@ -174,9 +204,25 @@ public function testReindexWithNotNullArgumentExecutesReindexEntities( ->method('create') ->will($this->returnValue($eavDecimal)); + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->willReturn(1); + $this->_model->reindex($ids); } + /** + * @return void + * @throws \Exception + */ + public function testReindexWithDisabledEavIndexer() + { + $this->scopeConfig->expects($this->once())->method('getValue')->willReturn(0); + $this->_eavSourceFactoryMock->expects($this->never())->method('create'); + $this->_eavDecimalFactoryMock->expects($this->never())->method('create'); + $this->_model->reindex(); + } + /** * @return array */ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php index c254557904da1..90c3f999a6a8b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php @@ -5,53 +5,87 @@ */ namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Eav\Action; +use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Indexer\BatchProviderInterface; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FullTest extends \PHPUnit\Framework\TestCase { - public function testExecuteWithAdapterErrorThrowsException() - { - $eavDecimalFactory = $this->createPartialMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory::class, - ['create'] - ); - $eavSourceFactory = $this->createPartialMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory::class, - ['create'] - ); + /** + * @var \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full|\PHPUnit_Framework_MockObject_MockObject + */ + private $model; - $exceptionMessage = 'exception message'; - $exception = new \Exception($exceptionMessage); + /** + * @var DecimalFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavDecimalFactory; - $eavDecimalFactory->expects($this->once()) - ->method('create') - ->will($this->throwException($exception)); + /** + * @var SourceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavSourceFactory; - $metadataMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); - $batchProviderMock = $this->createMock(\Magento\Framework\Indexer\BatchProviderInterface::class); + /** + * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; - $batchManagementMock = $this->createMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator::class - ); + /** + * @var BatchProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $batchProvider; - $tableSwitcherMock = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class - )->disableOriginalConstructor()->getMock(); - - $model = new \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full( - $eavDecimalFactory, - $eavSourceFactory, - $metadataMock, - $batchProviderMock, - $batchManagementMock, - $tableSwitcherMock - ); + /** + * @var BatchSizeCalculator|\PHPUnit_Framework_MockObject_MockObject + */ + private $batchSizeCalculator; + + /** + * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + */ + private $activeTableSwitcher; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; - $this->expectException(\Magento\Framework\Exception\LocalizedException::class); - $this->expectExceptionMessage($exceptionMessage); + /** + * @return void + */ + protected function setUp() + { + $this->eavDecimalFactory = $this->createPartialMock(DecimalFactory::class, ['create']); + $this->eavSourceFactory = $this->createPartialMock(SourceFactory::class, ['create']); + $this->metadataPool = $this->createMock(MetadataPool::class); + $this->batchProvider = $this->getMockForAbstractClass(BatchProviderInterface::class); + $this->batchSizeCalculator = $this->createMock(BatchSizeCalculator::class); + $this->activeTableSwitcher = $this->createMock(ActiveTableSwitcher::class); + $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); - $model->execute(); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full::class, + [ + 'eavDecimalFactory' => $this->eavDecimalFactory, + 'eavSourceFactory' => $this->eavSourceFactory, + 'metadataPool' => $this->metadataPool, + 'batchProvider' => $this->batchProvider, + 'batchSizeCalculator' => $this->batchSizeCalculator, + 'activeTableSwitcher' => $this->activeTableSwitcher, + 'scopeConfig' => $this->scopeConfig + ] + ); } /** @@ -59,14 +93,7 @@ public function testExecuteWithAdapterErrorThrowsException() */ public function testExecute() { - $eavDecimalFactory = $this->createPartialMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory::class, - ['create'] - ); - $eavSourceFactory = $this->createPartialMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory::class, - ['create'] - ); + $this->scopeConfig->expects($this->once())->method('getValue')->willReturn(1); $ids = [1, 2, 3]; $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) @@ -90,42 +117,29 @@ public function testExecute() $eavSource->expects($this->atLeastOnce())->method('getConnection')->willReturn($connectionMock); $eavDecimal->expects($this->atLeastOnce())->method('getConnection')->willReturn($connectionMock); - $eavDecimal->expects($this->once()) - ->method('reindexEntities') - ->with($ids); + $eavDecimal->expects($this->once())->method('reindexEntities')->with($ids); - $eavSource->expects($this->once()) - ->method('reindexEntities') - ->with($ids); + $eavSource->expects($this->once())->method('reindexEntities')->with($ids); - $eavDecimalFactory->expects($this->once()) - ->method('create') - ->will($this->returnValue($eavSource)); + $this->eavDecimalFactory->expects($this->once())->method('create')->will($this->returnValue($eavSource)); - $eavSourceFactory->expects($this->once()) - ->method('create') - ->will($this->returnValue($eavDecimal)); + $this->eavSourceFactory->expects($this->once())->method('create')->will($this->returnValue($eavDecimal)); - $metadataMock = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); $entityMetadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) ->getMockForAbstractClass(); - $metadataMock->expects($this->atLeastOnce()) + $this->metadataPool->expects($this->atLeastOnce()) ->method('getMetadata') ->with(\Magento\Catalog\Api\Data\ProductInterface::class) ->willReturn($entityMetadataMock); - $batchProviderMock = $this->createMock(\Magento\Framework\Indexer\BatchProviderInterface::class); - $batchProviderMock->expects($this->atLeastOnce()) + $this->batchProvider->expects($this->atLeastOnce()) ->method('getBatches') ->willReturn([['from' => 10, 'to' => 100]]); - $batchProviderMock->expects($this->atLeastOnce()) + $this->batchProvider->expects($this->atLeastOnce()) ->method('getBatchIds') ->willReturn($ids); - $batchManagementMock = $this->createMock( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator::class - ); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() ->getMock(); @@ -134,19 +148,17 @@ public function testExecute() $selectMock->expects($this->atLeastOnce())->method('distinct')->willReturnSelf(); $selectMock->expects($this->atLeastOnce())->method('from')->willReturnSelf(); - $tableSwitcherMock = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class - )->disableOriginalConstructor()->getMock(); - - $model = new \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full( - $eavDecimalFactory, - $eavSourceFactory, - $metadataMock, - $batchProviderMock, - $batchManagementMock, - $tableSwitcherMock - ); + $this->model->execute(); + } - $model->execute(); + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testExecuteWithDisabledEavIndexer() + { + $this->scopeConfig->expects($this->once())->method('getValue')->willReturn(0); + $this->metadataPool->expects($this->never())->method('getMetadata'); + $this->model->execute(); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/WebsiteTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/WebsiteTest.php index d551822d975ea..f64789a2a3d82 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/WebsiteTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/WebsiteTest.php @@ -5,43 +5,179 @@ */ namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Price\Plugin; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; + class WebsiteTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ - protected $_objectManager; + protected $objectManager; /** * @var \Magento\Catalog\Model\Indexer\Product\Price\Plugin\Website */ - protected $_model; + protected $model; + + /** + * @var \Magento\Framework\Indexer\DimensionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dimensionFactory; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|\PHPUnit_Framework_MockObject_MockObject + */ + protected $tableMaintainer; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor|\PHPUnit_Framework_MockObject_MockObject + * @var DimensionModeConfiguration|\PHPUnit_Framework_MockObject_MockObject */ - protected $_priceProcessorMock; + protected $dimensionModeConfiguration; protected function setUp() { - $this->_objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_priceProcessorMock = $this->createPartialMock( - \Magento\Catalog\Model\Indexer\Product\Price\Processor::class, - ['markIndexerAsInvalid'] + $this->dimensionFactory = $this->createPartialMock( + \Magento\Framework\Indexer\DimensionFactory::class, + ['create'] ); - $this->_model = $this->_objectManager->getObject( + $this->tableMaintainer = $this->createPartialMock( + \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class, + ['dropTablesForDimensions', 'createTablesForDimensions'] + ); + + $this->dimensionModeConfiguration = $this->createPartialMock( + DimensionModeConfiguration::class, + ['getDimensionConfiguration'] + ); + + $this->model = $this->objectManager->getObject( \Magento\Catalog\Model\Indexer\Product\Price\Plugin\Website::class, - ['processor' => $this->_priceProcessorMock] + [ + 'dimensionFactory' => $this->dimensionFactory, + 'tableMaintainer' => $this->tableMaintainer, + 'dimensionModeConfiguration' => $this->dimensionModeConfiguration, + ] ); } public function testAfterDelete() { - $this->_priceProcessorMock->expects($this->once())->method('markIndexerAsInvalid'); + $dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + + $this->dimensionFactory->expects($this->once())->method('create')->willReturn( + $dimensionMock + ); + $this->tableMaintainer->expects($this->once())->method('dropTablesForDimensions')->with( + [$dimensionMock] + ); + + $this->dimensionModeConfiguration->expects($this->once())->method('getDimensionConfiguration')->willReturn( + [\Magento\Store\Model\Indexer\WebsiteDimensionProvider::DIMENSION_NAME] + ); + + $subjectMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $objectResourceMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $websiteMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->assertEquals( + $objectResourceMock, + $this->model->afterDelete($subjectMock, $objectResourceMock, $websiteMock) + ); + } + + public function testAfterDeleteOnModeWithoutWebsiteDimension() + { + $dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + + $this->dimensionFactory->expects($this->never())->method('create')->willReturn( + $dimensionMock + ); + $this->tableMaintainer->expects($this->never())->method('dropTablesForDimensions')->with( + [$dimensionMock] + ); - $websiteMock = $this->createMock(\Magento\Store\Model\ResourceModel\Website::class); - $this->assertEquals('return_value', $this->_model->afterDelete($websiteMock, 'return_value')); + $this->dimensionModeConfiguration->expects($this->once())->method('getDimensionConfiguration')->willReturn( + [] + ); + + $subjectMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $objectResourceMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $websiteMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->assertEquals( + $objectResourceMock, + $this->model->afterDelete($subjectMock, $objectResourceMock, $websiteMock) + ); + } + + public function testAfterSave() + { + $dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + + $this->dimensionFactory->expects($this->once())->method('create')->willReturn( + $dimensionMock + ); + $this->tableMaintainer->expects($this->once())->method('createTablesForDimensions')->with( + [$dimensionMock] + ); + + $this->dimensionModeConfiguration->expects($this->once())->method('getDimensionConfiguration')->willReturn( + [\Magento\Store\Model\Indexer\WebsiteDimensionProvider::DIMENSION_NAME] + ); + + $subjectMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $objectResourceMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $websiteMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $websiteMock->expects($this->once()) + ->method('isObjectNew') + ->willReturn(true); + + $this->assertEquals( + $objectResourceMock, + $this->model->afterSave($subjectMock, $objectResourceMock, $websiteMock) + ); + } + + public function testAfterSaveOnModeWithoutWebsiteDimension() + { + $dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + + $this->dimensionFactory->expects($this->never())->method('create')->willReturn( + $dimensionMock + ); + $this->tableMaintainer->expects($this->never())->method('createTablesForDimensions')->with( + [$dimensionMock] + ); + + $this->dimensionModeConfiguration->expects($this->once())->method('getDimensionConfiguration')->willReturn( + [] + ); + + $subjectMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $objectResourceMock = $this->createMock(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class); + $websiteMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + $websiteMock->expects($this->once()) + ->method('isObjectNew') + ->willReturn(true); + + $this->assertEquals( + $objectResourceMock, + $this->model->afterSave($subjectMock, $objectResourceMock, $websiteMock) + ); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php index 3cc6f94d58c29..1b42b09e5dd32 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php @@ -10,7 +10,6 @@ use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Catalog\Model\Product\Attribute\Repository; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; -use Magento\Eav\Api\Data\AttributeFrontendLabelInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -72,6 +71,9 @@ class RepositoryTest extends \PHPUnit\Framework\TestCase */ private $optionManagementMock; + /** + * @inheritdoc + */ protected function setUp() { $this->attributeResourceMock = @@ -116,6 +118,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testGet() { $attributeCode = 'some attribute code'; @@ -128,6 +133,9 @@ public function testGet() $this->model->get($attributeCode); } + /** + * @return void + */ public function testGetList() { $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); @@ -141,6 +149,9 @@ public function testGetList() $this->model->getList($searchCriteriaMock); } + /** + * @return void + */ public function testDelete() { $attributeMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); @@ -149,6 +160,9 @@ public function testDelete() $this->assertEquals(true, $this->model->delete($attributeMock)); } + /** + * @return void + */ public function testDeleteById() { $attributeCode = 'some attribute code'; @@ -164,6 +178,9 @@ public function testDeleteById() $this->assertEquals(true, $this->model->deleteById($attributeCode)); } + /** + * @return void + */ public function testGetCustomAttributesMetadata() { $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteria::class); @@ -235,15 +252,18 @@ public function testSaveInputExceptionInvalidFieldValue() ); $attributeMock->expects($this->once())->method('getAttributeId')->willReturn(null); $attributeMock->expects($this->once())->method('setAttributeId')->with(null)->willReturnSelf(); - $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeFrontendLabelInterface::class); - $attributeMock->expects($this->exactly(4))->method('getFrontendLabels')->willReturn([$labelMock]); - $attributeMock->expects($this->exactly(2))->method('getDefaultFrontendLabel')->willReturn('test'); + $labelMock = $this->createMock(\Magento\Eav\Model\Entity\Attribute\FrontendLabel::class); + $attributeMock->expects($this->any())->method('getFrontendLabels')->willReturn([$labelMock]); + $attributeMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn(null); $labelMock->expects($this->once())->method('getStoreId')->willReturn(0); $labelMock->expects($this->once())->method('getLabel')->willReturn(null); $this->model->save($attributeMock); } + /** + * @return void + */ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload() { $attributeId = 1; @@ -260,7 +280,7 @@ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload() ->method('get') ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode) ->willReturn($existingModelMock); - + $existingModelMock->expects($this->once())->method('getDefaultFrontendLabel')->willReturn('default_label'); // Attribute code must not be changed after attribute creation $attributeMock->expects($this->once())->method('setAttributeCode')->with($attributeCode); $this->attributeResourceMock->expects($this->once())->method('save')->with($attributeMock); @@ -269,9 +289,12 @@ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload() $this->model->save($attributeMock); } + /** + * @return void + */ public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload() { - $labelMock = $this->createMock(AttributeFrontendLabelInterface::class); + $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeFrontendLabelInterface::class); $labelMock->expects($this->any())->method('getStoreId')->willReturn(1); $labelMock->expects($this->any())->method('getLabel')->willReturn('Store Scope Label'); @@ -280,11 +303,12 @@ public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload() $attributeMock = $this->createMock(Attribute::class); $attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); $attributeMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId); - $attributeMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn('Default Label'); + $attributeMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn(null); $attributeMock->expects($this->any())->method('getFrontendLabels')->willReturn([$labelMock]); $attributeMock->expects($this->any())->method('getOptions')->willReturn([]); $existingModelMock = $this->createMock(Attribute::class); + $existingModelMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn('Default Label'); $existingModelMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId); $existingModelMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); @@ -295,12 +319,7 @@ public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload() $attributeMock->expects($this->once()) ->method('setDefaultFrontendLabel') - ->with( - [ - 0 => 'Default Label', - 1 => 'Store Scope Label' - ] - ); + ->with('Default Label'); $this->attributeResourceMock->expects($this->once())->method('save')->with($attributeMock); $this->model->save($attributeMock); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php index 1eb5f1a2dacd2..da6b790fedfa6 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php @@ -18,6 +18,9 @@ class DefaultValidatorTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @inheritdoc + */ protected function setUp() { $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); @@ -60,31 +63,30 @@ public function isValidTitleDataProvider() { $mess = ['option required fields' => 'Missed values for option required fields']; return [ - ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), [], true], - [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], + ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), [], true], + [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], ]; } /** - * @param $title - * @param $type - * @param $priceType - * @param $price - * @param $product - * @param $messages - * @param $result + * @param string $title + * @param string $type + * @param string $priceType + * @param \Magento\Framework\DataObject $product + * @param array $messages + * @param bool $result * @dataProvider isValidTitleDataProvider */ - public function testIsValidTitle($title, $type, $priceType, $price, $product, $messages, $result) + public function testIsValidTitle($title, $type, $priceType, $product, $messages, $result) { - $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; + $methods = ['getTitle', 'getType', 'getPriceType', '__wakeup', 'getProduct']; $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); $valueMock->expects($this->any())->method('getType')->will($this->returnValue($type)); $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); - $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); + // $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); $this->assertEquals($result, $this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); @@ -104,7 +106,7 @@ public function isValidFailDataProvider() } /** - * @param $product + * @param \Magento\Framework\DataObject $product * @dataProvider isValidFailDataProvider */ public function testIsValidFail($product) @@ -124,41 +126,4 @@ public function testIsValidFail($product) $this->assertFalse($this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); } - - /** - * Data provider for testValidationNegativePrice - * @return array - */ - public function validationNegativePriceDataProvider() - { - return [ - ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 1])], - ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 0])], - ]; - } - - /** - * @param $title - * @param $type - * @param $priceType - * @param $price - * @param $product - * @dataProvider validationNegativePriceDataProvider - */ - public function testValidationNegativePrice($title, $type, $priceType, $price, $product) - { - $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; - $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); - $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); - $valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue($type)); - $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); - $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); - $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); - - $messages = [ - 'option values' => 'Invalid option value', - ]; - $this->assertFalse($this->validator->isValid($valueMock)); - $this->assertEquals($messages, $this->validator->getMessages()); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php index 3c06db0e7ce5f..2de993c075514 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php @@ -18,6 +18,9 @@ class FileTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @inheritdoc + */ protected function setUp() { $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); @@ -54,24 +57,34 @@ protected function setUp() ); } + /** + * @return void + */ public function testIsValidSuccess() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); $this->valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue('name 1.1')); - $this->valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue('fixed')); - $this->valueMock->expects($this->once())->method('getPrice')->will($this->returnValue(10)); + $this->valueMock->method('getPriceType') + ->willReturn('fixed'); + $this->valueMock->method('getPrice') + ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(15)); $this->assertEmpty($this->validator->getMessages()); $this->assertTrue($this->validator->isValid($this->valueMock)); } + /** + * @return void + */ public function testIsValidWithNegativeImageSize() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); $this->valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue('name 1.1')); - $this->valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue('fixed')); - $this->valueMock->expects($this->once())->method('getPrice')->will($this->returnValue(10)); + $this->valueMock->method('getPriceType') + ->willReturn('fixed'); + $this->valueMock->method('getPrice') + ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(-10)); $this->valueMock->expects($this->never())->method('getImageSizeY'); $messages = [ @@ -81,12 +94,17 @@ public function testIsValidWithNegativeImageSize() $this->assertEquals($messages, $this->validator->getMessages()); } + /** + * @return void + */ public function testIsValidWithNegativeImageSizeY() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); $this->valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue('name 1.1')); - $this->valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue('fixed')); - $this->valueMock->expects($this->once())->method('getPrice')->will($this->returnValue(10)); + $this->valueMock->method('getPriceType') + ->willReturn('fixed'); + $this->valueMock->method('getPrice') + ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(-10)); $messages = [ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php index 95a9b961c8d81..b97783edf856c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php @@ -18,6 +18,9 @@ class SelectTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @inheritdoc + */ protected function setUp() { $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); @@ -90,7 +93,7 @@ public function isValidSuccessDataProvider() ] ], [ - false, + true, [ 'title' => 'Some Title', 'price_type' => 'fixed', @@ -100,6 +103,9 @@ public function isValidSuccessDataProvider() ]; } + /** + * @return void + */ public function testIsValidateWithInvalidOptionValues() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); @@ -118,6 +124,9 @@ public function testIsValidateWithInvalidOptionValues() $this->assertEquals($messages, $this->validator->getMessages()); } + /** + * @return void + */ public function testIsValidateWithEmptyValues() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); @@ -163,7 +172,6 @@ public function testIsValidateWithInvalidData($priceType, $price, $title) public function isValidateWithInvalidDataDataProvider() { return [ - 'invalid_price' => ['fixed', -10, 'Title'], 'invalid_price_type' => ['some_value', '10', 'Title'], 'empty_title' => ['fixed', 10, null] ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php index cf31d67817684..4881154728ddc 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php @@ -18,6 +18,9 @@ class TextTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @inheritdoc + */ protected function setUp() { $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); @@ -54,23 +57,33 @@ protected function setUp() ); } + /** + * @return void + */ public function testIsValidSuccess() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); $this->valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue('name 1.1')); - $this->valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue('fixed')); - $this->valueMock->expects($this->once())->method('getPrice')->will($this->returnValue(10)); + $this->valueMock->method('getPriceType') + ->willReturn('fixed'); + $this->valueMock->method('getPrice') + ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(10)); $this->assertTrue($this->validator->isValid($this->valueMock)); $this->assertEmpty($this->validator->getMessages()); } + /** + * @return void + */ public function testIsValidWithNegativeMaxCharacters() { $this->valueMock->expects($this->once())->method('getTitle')->will($this->returnValue('option_title')); $this->valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue('name 1.1')); - $this->valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue('fixed')); - $this->valueMock->expects($this->once())->method('getPrice')->will($this->returnValue(10)); + $this->valueMock->method('getPriceType') + ->willReturn('fixed'); + $this->valueMock->method('getPrice') + ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(-10)); $messages = [ 'option values' => 'Invalid option value', diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index 3fc3587637dad..c729a0c58e1ec 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -4,16 +4,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Test\Unit\Model; -use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\Data\ProductExtensionInterface; +use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; +use Magento\Catalog\Model\Product\LinkTypeProvider; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ProductRepository; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\ImageContentValidator; +use Magento\Framework\Api\ImageContentValidatorInterface; +use Magento\Framework\Api\ImageProcessorInterface; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\DB\Adapter\ConnectionException; +use Magento\Framework\Filesystem; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class ProductRepositoryTest @@ -25,127 +45,127 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ - protected $productMock; + protected $product; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ - protected $initializedProductMock; + private $initializedProduct; /** - * @var \Magento\Catalog\Model\ProductRepository + * @var ProductRepository */ - protected $model; + private $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Helper|MockObject */ - protected $initializationHelperMock; + private $initializationHelper; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ - protected $resourceModelMock; + private $resourceModel; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductFactory|MockObject */ - protected $productFactoryMock; + private $productFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|MockObject */ - protected $collectionFactoryMock; + private $collectionFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var SearchCriteriaBuilder|MockObject */ - protected $searchCriteriaBuilderMock; + private $searchCriteriaBuilder; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var FilterBuilder|MockObject */ - protected $filterBuilderMock; + private $filterBuilder; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductAttributeRepositoryInterface|MockObject */ - protected $metadataServiceMock; + private $metadataService; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductSearchResultsInterfaceFactory|MockObject */ - protected $searchResultsFactoryMock; + private $searchResultsFactory; /** - * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + * @var ExtensibleDataObjectConverter|MockObject */ - protected $eavConfigMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $extensibleDataObjectConverterMock; + private $extensibleDataObjectConverter; /** * @var array data to create product */ - protected $productData = [ + private $productData = [ 'sku' => 'exisiting', 'name' => 'existing product', ]; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Filesystem + * @var Filesystem|MockObject */ - protected $fileSystemMock; + private $fileSystem; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap + * @var MimeTypeExtensionMap|MockObject */ - protected $mimeTypeExtensionMapMock; + private $mimeTypeExtensionMap; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ImageContentInterfaceFactory|MockObject */ - protected $contentFactoryMock; + private $contentFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Api\ImageContentValidator + * @var ImageContentValidator|MockObject */ - protected $contentValidatorMock; + private $contentValidator; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var LinkTypeProvider|MockObject */ - protected $linkTypeProviderMock; + private $linkTypeProvider; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Api\ImageProcessorInterface + * @var ImageProcessorInterface|MockObject */ - protected $imageProcessorMock; + private $imageProcessor; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var ObjectManager */ - protected $objectManager; + private $objectManager; /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ - protected $storeManagerMock; + private $storeManager; /** * @var \Magento\Catalog\Model\Product\Gallery\Processor|\PHPUnit_Framework_MockObject_MockObject */ - protected $mediaGalleryProcessor; + private $mediaGalleryProcessor; /** * @var CollectionProcessorInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $collectionProcessorMock; + private $collectionProcessor; + + /** + * @var ProductExtensionInterface|MockObject + */ + private $productExtension; /** * @var Json|\PHPUnit_Framework_MockObject_MockObject @@ -164,12 +184,12 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->productFactoryMock = $this->createPartialMock( + $this->productFactory = $this->createPartialMock( \Magento\Catalog\Model\ProductFactory::class, ['create', 'setData'] ); - $this->productMock = $this->createPartialMock( + $this->product = $this->createPartialMock( \Magento\Catalog\Model\Product::class, [ 'getId', @@ -179,11 +199,12 @@ protected function setUp() 'load', 'setData', 'getStoreId', - 'getMediaGalleryEntries' + 'getMediaGalleryEntries', + 'getExtensionAttributes' ] ); - $this->initializedProductMock = $this->createPartialMock( + $this->initializedProduct = $this->createPartialMock( \Magento\Catalog\Model\Product::class, [ 'getWebsiteIds', @@ -199,66 +220,66 @@ protected function setUp() 'validate', 'save', 'getMediaGalleryEntries', + 'getExtensionAttributes' ] ); - $this->initializedProductMock->expects($this->any()) + $this->initializedProduct->expects($this->any()) ->method('hasGalleryAttribute') ->willReturn(true); - $this->filterBuilderMock = $this->createMock(\Magento\Framework\Api\FilterBuilder::class); - $this->initializationHelperMock = $this->createMock( - \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::class - ); - $this->collectionFactoryMock = $this->createPartialMock( - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class, - ['create'] - ); - $this->searchCriteriaBuilderMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaBuilder::class); - $this->metadataServiceMock = $this->createMock(\Magento\Catalog\Api\ProductAttributeRepositoryInterface::class); - $this->searchResultsFactoryMock = $this->createPartialMock( + $this->filterBuilder = $this->createMock(FilterBuilder::class); + $this->initializationHelper = $this->createMock(Helper::class); + $this->collectionFactory = $this->createPartialMock(CollectionFactory::class, ['create']); + $this->searchCriteriaBuilder = $this->createMock(SearchCriteriaBuilder::class); + $this->metadataService = $this->createMock(ProductAttributeRepositoryInterface::class); + $this->searchResultsFactory = $this->createPartialMock( \Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory::class, ['create'] ); - $this->resourceModelMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); + $this->resourceModel = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); $this->objectManager = new ObjectManager($this); - $this->extensibleDataObjectConverterMock = $this - ->getMockBuilder(\Magento\Framework\Api\ExtensibleDataObjectConverter::class) + $this->extensibleDataObjectConverter = $this + ->getMockBuilder(ExtensibleDataObjectConverter::class) ->setMethods(['toNestedArray']) ->disableOriginalConstructor() ->getMock(); - $this->fileSystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + $this->fileSystem = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor()->getMock(); - $this->mimeTypeExtensionMapMock = - $this->getMockBuilder(\Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap::class)->getMock(); - $this->contentFactoryMock = $this->createPartialMock( - \Magento\Framework\Api\Data\ImageContentInterfaceFactory::class, - ['create'] - ); - $this->contentValidatorMock = $this->getMockBuilder( - \Magento\Framework\Api\ImageContentValidatorInterface::class - ) + $this->mimeTypeExtensionMap = $this->getMockBuilder(MimeTypeExtensionMap::class)->getMock(); + $this->contentFactory = $this->createPartialMock(ImageContentInterfaceFactory::class, ['create']); + $this->contentValidator = $this->getMockBuilder(ImageContentValidatorInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->linkTypeProviderMock = $this->createPartialMock( - \Magento\Catalog\Model\Product\LinkTypeProvider::class, - ['getLinkTypes'] - ); - $this->imageProcessorMock = $this->createMock(\Magento\Framework\Api\ImageProcessorInterface::class); + $this->linkTypeProvider = $this->createPartialMock(LinkTypeProvider::class, ['getLinkTypes']); + $this->imageProcessor = $this->createMock(ImageProcessorInterface::class); - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) ->disableOriginalConstructor() ->setMethods([]) ->getMockForAbstractClass(); - $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + $this->productExtension = $this->getMockBuilder(ProductExtensionInterface::class) + ->setMethods(['__toArray']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->productExtension + ->method('__toArray') + ->willReturn([]); + $this->product + ->method('getExtensionAttributes') + ->willReturn($this->productExtension); + $this->initializedProduct + ->method('getExtensionAttributes') + ->willReturn($this->productExtension); + $storeMock = $this->getMockBuilder(StoreInterface::class) ->disableOriginalConstructor() ->setMethods([]) ->getMockForAbstractClass(); $storeMock->expects($this->any())->method('getWebsiteId')->willReturn('1'); $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); - $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); + $this->storeManager->expects($this->any())->method('getStore')->willReturn($storeMock); $this->mediaGalleryProcessor = $this->createMock(\Magento\Catalog\Model\Product\Gallery\Processor::class); - $this->collectionProcessorMock = $this->getMockBuilder(CollectionProcessorInterface::class) + $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); $this->serializerMock = $this->getMockBuilder(Json::class)->getMock(); @@ -273,26 +294,26 @@ function ($value) { ); $this->model = $this->objectManager->getObject( - \Magento\Catalog\Model\ProductRepository::class, + ProductRepository::class, [ - 'productFactory' => $this->productFactoryMock, - 'initializationHelper' => $this->initializationHelperMock, - 'resourceModel' => $this->resourceModelMock, - 'filterBuilder' => $this->filterBuilderMock, - 'collectionFactory' => $this->collectionFactoryMock, - 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, - 'metadataServiceInterface' => $this->metadataServiceMock, - 'searchResultsFactory' => $this->searchResultsFactoryMock, - 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverterMock, - 'contentValidator' => $this->contentValidatorMock, - 'fileSystem' => $this->fileSystemMock, - 'contentFactory' => $this->contentFactoryMock, - 'mimeTypeExtensionMap' => $this->mimeTypeExtensionMapMock, - 'linkTypeProvider' => $this->linkTypeProviderMock, - 'imageProcessor' => $this->imageProcessorMock, - 'storeManager' => $this->storeManagerMock, + 'productFactory' => $this->productFactory, + 'initializationHelper' => $this->initializationHelper, + 'resourceModel' => $this->resourceModel, + 'filterBuilder' => $this->filterBuilder, + 'collectionFactory' => $this->collectionFactory, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilder, + 'metadataServiceInterface' => $this->metadataService, + 'searchResultsFactory' => $this->searchResultsFactory, + 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, + 'contentValidator' => $this->contentValidator, + 'fileSystem' => $this->fileSystem, + 'contentFactory' => $this->contentFactory, + 'mimeTypeExtensionMap' => $this->mimeTypeExtensionMap, + 'linkTypeProvider' => $this->linkTypeProvider, + 'imageProcessor' => $this->imageProcessor, + 'storeManager' => $this->storeManager, 'mediaGalleryProcessor' => $this->mediaGalleryProcessor, - 'collectionProcessor' => $this->collectionProcessorMock, + 'collectionProcessor' => $this->collectionProcessor, 'serializer' => $this->serializerMock, 'cacheLimit' => $this->cacheLimit ] @@ -305,50 +326,50 @@ function ($value) { */ public function testGetAbsentProduct() { - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with('test_sku') + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with('test_sku') ->will($this->returnValue(null)); - $this->productFactoryMock->expects($this->never())->method('setData'); + $this->productFactory->expects($this->never())->method('setData'); $this->model->get('test_sku'); } public function testCreateCreatesProduct() { $sku = 'test_sku'; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with($sku) + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with($sku) ->will($this->returnValue('test_id')); - $this->productMock->expects($this->once())->method('load')->with('test_id'); - $this->productMock->expects($this->once())->method('getSku')->willReturn($sku); - $this->assertEquals($this->productMock, $this->model->get($sku)); + $this->product->expects($this->once())->method('load')->with('test_id'); + $this->product->expects($this->once())->method('getSku')->willReturn($sku); + $this->assertEquals($this->product, $this->model->get($sku)); } public function testGetProductInEditMode() { $sku = 'test_sku'; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with($sku) + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with($sku) ->will($this->returnValue('test_id')); - $this->productMock->expects($this->once())->method('setData')->with('_edit_mode', true); - $this->productMock->expects($this->once())->method('load')->with('test_id'); - $this->productMock->expects($this->once())->method('getSku')->willReturn($sku); - $this->assertEquals($this->productMock, $this->model->get($sku, true)); + $this->product->expects($this->once())->method('setData')->with('_edit_mode', true); + $this->product->expects($this->once())->method('load')->with('test_id'); + $this->product->expects($this->once())->method('getSku')->willReturn($sku); + $this->assertEquals($this->product, $this->model->get($sku, true)); } public function testGetBySkuWithSpace() { $trimmedSku = 'test_sku'; $sku = 'test_sku '; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with($sku) + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with($sku) ->will($this->returnValue('test_id')); - $this->productMock->expects($this->once())->method('load')->with('test_id'); - $this->productMock->expects($this->once())->method('getSku')->willReturn($trimmedSku); - $this->assertEquals($this->productMock, $this->model->get($sku)); + $this->product->expects($this->once())->method('load')->with('test_id'); + $this->product->expects($this->once())->method('getSku')->willReturn($trimmedSku); + $this->assertEquals($this->product, $this->model->get($sku)); } public function testGetWithSetStoreId() @@ -356,13 +377,13 @@ public function testGetWithSetStoreId() $productId = 123; $sku = 'test-sku'; $storeId = 7; - $this->productFactoryMock->expects($this->once())->method('create')->willReturn($this->productMock); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with($sku)->willReturn($productId); - $this->productMock->expects($this->once())->method('setData')->with('store_id', $storeId); - $this->productMock->expects($this->once())->method('load')->with($productId); - $this->productMock->expects($this->once())->method('getId')->willReturn($productId); - $this->productMock->expects($this->once())->method('getSku')->willReturn($sku); - $this->assertSame($this->productMock, $this->model->get($sku, false, $storeId)); + $this->productFactory->expects($this->once())->method('create')->willReturn($this->product); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with($sku)->willReturn($productId); + $this->product->expects($this->once())->method('setData')->with('store_id', $storeId); + $this->product->expects($this->once())->method('load')->with($productId); + $this->product->expects($this->once())->method('getId')->willReturn($productId); + $this->product->expects($this->once())->method('getSku')->willReturn($sku); + $this->assertSame($this->product, $this->model->get($sku, false, $storeId)); } /** @@ -371,22 +392,22 @@ public function testGetWithSetStoreId() */ public function testGetByIdAbsentProduct() { - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->once())->method('load')->with('product_id'); - $this->productMock->expects($this->once())->method('getId')->willReturn(null); + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->product->expects($this->once())->method('load')->with('product_id'); + $this->product->expects($this->once())->method('getId')->willReturn(null); $this->model->getById('product_id'); } public function testGetByIdProductInEditMode() { $productId = 123; - $this->productFactoryMock->method('create')->willReturn($this->productMock); - $this->productMock->method('setData')->with('_edit_mode', true); - $this->productMock->method('load')->with($productId); - $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId); - $this->productMock->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->getById($productId, true)); + $this->productFactory->method('create')->willReturn($this->product); + $this->product->method('setData')->with('_edit_mode', true); + $this->product->method('load')->with($productId); + $this->product->expects($this->atLeastOnce())->method('getId')->willReturn($productId); + $this->product->method('getSku')->willReturn('simple'); + $this->assertEquals($this->product, $this->model->getById($productId, true)); } /** @@ -400,21 +421,21 @@ public function testGetByIdProductInEditMode() public function testGetByIdForCacheKeyGenerate($identifier, $editMode, $storeId) { $callIndex = 0; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); if ($editMode) { - $this->productMock->expects($this->at($callIndex))->method('setData')->with('_edit_mode', $editMode); + $this->product->expects($this->at($callIndex))->method('setData')->with('_edit_mode', $editMode); ++$callIndex; } if ($storeId !== null) { - $this->productMock->expects($this->at($callIndex))->method('setData')->with('store_id', $storeId); + $this->product->expects($this->at($callIndex))->method('setData')->with('store_id', $storeId); } - $this->productMock->expects($this->once())->method('load')->with($identifier); - $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($identifier); - $this->productMock->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + $this->product->expects($this->once())->method('load')->with($identifier); + $this->product->expects($this->atLeastOnce())->method('getId')->willReturn($identifier); + $this->product->method('getSku')->willReturn('simple'); + $this->assertEquals($this->product, $this->model->getById($identifier, $editMode, $storeId)); //Second invocation should just return from cache - $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + $this->assertEquals($this->product, $this->model->getById($identifier, $editMode, $storeId)); } /** @@ -428,18 +449,18 @@ public function testGetByIdForcedReload() $editMode = false; $storeId = 0; - $this->productFactoryMock->expects($this->exactly(2))->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->exactly(2))->method('load'); + $this->productFactory->expects($this->exactly(2))->method('create') + ->will($this->returnValue($this->product)); + $this->product->expects($this->exactly(2))->method('load'); $this->serializerMock->expects($this->exactly(3))->method('serialize'); - $this->productMock->expects($this->exactly(4))->method('getId')->willReturn($identifier); - $this->productMock->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + $this->product->expects($this->exactly(4))->method('getId')->willReturn($identifier); + $this->product->method('getSku')->willReturn('simple'); + $this->assertEquals($this->product, $this->model->getById($identifier, $editMode, $storeId)); //second invocation should just return from cache - $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); + $this->assertEquals($this->product, $this->model->getById($identifier, $editMode, $storeId)); //force reload - $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId, true)); + $this->assertEquals($this->product, $this->model->getById($identifier, $editMode, $storeId, true)); } /** @@ -454,7 +475,7 @@ public function testGetByIdWhenCacheReduced() $productsCount = $this->cacheLimit * 2; $productMocks = $this->getProductMocksForReducedCache($productsCount); - $productFactoryInvMock = $this->productFactoryMock->expects($this->exactly($productsCount)) + $productFactoryInvMock = $this->productFactory->expects($this->exactly($productsCount)) ->method('create'); call_user_func_array([$productFactoryInvMock, 'willReturnOnConsecutiveCalls'], $productMocks); $this->serializerMock->expects($this->atLeastOnce())->method('serialize'); @@ -509,86 +530,86 @@ public function testGetForcedReload() $editMode = false; $storeId = 0; - $this->productFactoryMock->expects($this->exactly(2))->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->exactly(2))->method('load'); - $this->productMock->expects($this->exactly(2))->method('getId')->willReturn($sku); - $this->resourceModelMock->expects($this->exactly(2))->method('getIdBySku') + $this->productFactory->expects($this->exactly(2))->method('create') + ->will($this->returnValue($this->product)); + $this->product->expects($this->exactly(2))->method('load'); + $this->product->expects($this->exactly(2))->method('getId')->willReturn($sku); + $this->resourceModel->expects($this->exactly(2))->method('getIdBySku') ->with($sku)->willReturn($id); - $this->productMock->expects($this->exactly(2))->method('getSku')->willReturn($sku); + $this->product->expects($this->exactly(2))->method('getSku')->willReturn($sku); $this->serializerMock->expects($this->exactly(3))->method('serialize'); - $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId)); + $this->assertEquals($this->product, $this->model->get($sku, $editMode, $storeId)); //second invocation should just return from cache - $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId)); + $this->assertEquals($this->product, $this->model->get($sku, $editMode, $storeId)); //force reload - $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId, true)); + $this->assertEquals($this->product, $this->model->get($sku, $editMode, $storeId, true)); } public function testGetByIdWithSetStoreId() { $productId = 123; $storeId = 1; - $this->productFactoryMock->expects($this->atLeastOnce())->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->once())->method('setData')->with('store_id', $storeId); - $this->productMock->expects($this->once())->method('load')->with($productId); - $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId); - $this->productMock->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->getById($productId, false, $storeId)); + $this->productFactory->expects($this->atLeastOnce())->method('create') + ->will($this->returnValue($this->product)); + $this->product->expects($this->once())->method('setData')->with('store_id', $storeId); + $this->product->expects($this->once())->method('load')->with($productId); + $this->product->expects($this->atLeastOnce())->method('getId')->willReturn($productId); + $this->product->method('getSku')->willReturn('simple'); + $this->assertEquals($this->product, $this->model->getById($productId, false, $storeId)); } public function testGetBySkuFromCacheInitializedInGetById() { $productId = 123; $productSku = 'product_123'; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->once())->method('load')->with($productId); - $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId); - $this->productMock->expects($this->once())->method('getSku')->willReturn($productSku); - $this->assertEquals($this->productMock, $this->model->getById($productId)); - $this->assertEquals($this->productMock, $this->model->get($productSku)); + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->product->expects($this->once())->method('load')->with($productId); + $this->product->expects($this->atLeastOnce())->method('getId')->willReturn($productId); + $this->product->expects($this->once())->method('getSku')->willReturn($productSku); + $this->assertEquals($this->product, $this->model->getById($productId)); + $this->assertEquals($this->product, $this->model->get($productSku)); } public function testSaveExisting() { - $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->resourceModel->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock)->willReturn(true); - $this->extensibleDataObjectConverterMock + $this->resourceModel->expects($this->once())->method('save')->with($this->product)->willReturn(true); + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); + $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); - $this->assertEquals($this->productMock, $this->model->save($this->productMock)); + $this->assertEquals($this->product, $this->model->save($this->product)); } public function testSaveNew() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); - $this->resourceModelMock->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); + $this->resourceModel->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock)->willReturn(true); - $this->extensibleDataObjectConverterMock + $this->resourceModel->expects($this->once())->method('save')->with($this->product)->willReturn(true); + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->save($this->productMock)); + $this->assertEquals($this->product, $this->model->save($this->product)); } /** @@ -597,24 +618,24 @@ public function testSaveNew() */ public function testSaveUnableToSaveException() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->exactly(1)) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->exactly(1)) ->method('getIdBySku')->willReturn(null); - $this->productFactoryMock->expects($this->exactly(2)) + $this->productFactory->expects($this->exactly(2)) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock) + $this->resourceModel->expects($this->once())->method('save')->with($this->product) ->willThrowException(new \Exception()); - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->method('getSku')->willReturn('simple'); - $this->model->save($this->productMock); + $this->model->save($this->product); } /** @@ -623,24 +644,24 @@ public function testSaveUnableToSaveException() */ public function testSaveException() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); - $this->productFactoryMock->expects($this->exactly(2)) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); + $this->productFactory->expects($this->exactly(2)) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock) + $this->resourceModel->expects($this->once())->method('save')->with($this->product) ->willThrowException(new \Magento\Eav\Model\Entity\Attribute\Exception(__('123'))); - $this->productMock->expects($this->once())->method('getId')->willReturn(null); - $this->extensibleDataObjectConverterMock + $this->product->expects($this->once())->method('getId')->willReturn(null); + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->method('getSku')->willReturn('simple'); - $this->model->save($this->productMock); + $this->model->save($this->product); } /** @@ -649,22 +670,22 @@ public function testSaveException() */ public function testSaveInvalidProductException() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); - $this->productFactoryMock->expects($this->exactly(2)) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); + $this->productFactory->expects($this->exactly(2)) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(['error1', 'error2']); - $this->productMock->expects($this->never())->method('getId'); - $this->extensibleDataObjectConverterMock + $this->product->expects($this->never())->method('getId'); + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->method('getSku')->willReturn('simple'); - $this->model->save($this->productMock); + $this->model->save($this->product); } /** @@ -673,36 +694,36 @@ public function testSaveInvalidProductException() */ public function testSaveThrowsTemporaryStateExceptionIfDatabaseConnectionErrorOccurred() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->productFactoryMock->expects($this->any()) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never()) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never()) ->method('initialize'); - $this->resourceModelMock->expects($this->once()) + $this->resourceModel->expects($this->once()) ->method('validate') - ->with($this->productMock) + ->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once()) + $this->resourceModel->expects($this->once()) ->method('save') - ->with($this->productMock) + ->with($this->product) ->willThrowException(new ConnectionException('Connection lost')); - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->method('getSku')->willReturn('simple'); - $this->model->save($this->productMock); + $this->model->save($this->product); } public function testDelete() { - $this->productMock->expects($this->exactly(2))->method('getSku')->willReturn('product-42'); - $this->productMock->expects($this->exactly(2))->method('getId')->willReturn(42); - $this->resourceModelMock->expects($this->once())->method('delete')->with($this->productMock) + $this->product->expects($this->exactly(2))->method('getSku')->willReturn('product-42'); + $this->product->expects($this->exactly(2))->method('getId')->willReturn(42); + $this->resourceModel->expects($this->once())->method('delete')->with($this->product) ->willReturn(true); - $this->assertTrue($this->model->delete($this->productMock)); + $this->assertTrue($this->model->delete($this->product)); } /** @@ -711,22 +732,22 @@ public function testDelete() */ public function testDeleteException() { - $this->productMock->expects($this->exactly(2))->method('getSku')->willReturn('product-42'); - $this->productMock->expects($this->exactly(2))->method('getId')->willReturn(42); - $this->resourceModelMock->expects($this->once())->method('delete')->with($this->productMock) + $this->product->expects($this->exactly(2))->method('getSku')->willReturn('product-42'); + $this->product->expects($this->exactly(2))->method('getId')->willReturn(42); + $this->resourceModel->expects($this->once())->method('delete')->with($this->product) ->willThrowException(new \Exception()); - $this->model->delete($this->productMock); + $this->model->delete($this->product); } public function testDeleteById() { $sku = 'product-42'; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->resourceModelMock->expects($this->once())->method('getIdBySku')->with($sku) + $this->productFactory->expects($this->once())->method('create') + ->will($this->returnValue($this->product)); + $this->resourceModel->expects($this->once())->method('getIdBySku')->with($sku) ->will($this->returnValue('42')); - $this->productMock->expects($this->once())->method('load')->with('42'); - $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); + $this->product->expects($this->once())->method('load')->with('42'); + $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); $this->assertTrue($this->model->deleteById($sku)); } @@ -734,24 +755,24 @@ public function testGetList() { $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); - $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($collectionMock); - $this->productMock->method('getSku')->willReturn('simple'); + $this->collectionFactory->expects($this->once())->method('create')->willReturn($collectionMock); + $this->product->method('getSku')->willReturn('simple'); $collectionMock->expects($this->once())->method('addAttributeToSelect')->with('*'); $collectionMock->expects($this->exactly(2))->method('joinAttribute')->withConsecutive( ['status', 'catalog_product/status', 'entity_id', null, 'inner'], ['visibility', 'catalog_product/visibility', 'entity_id', null, 'inner'] ); - $this->collectionProcessorMock->expects($this->once()) + $this->collectionProcessor->expects($this->once()) ->method('process') ->with($searchCriteriaMock, $collectionMock); $collectionMock->expects($this->once())->method('load'); $collectionMock->expects($this->once())->method('addCategoryIds'); - $collectionMock->expects($this->atLeastOnce())->method('getItems')->willReturn([$this->productMock]); + $collectionMock->expects($this->atLeastOnce())->method('getItems')->willReturn([$this->product]); $collectionMock->expects($this->once())->method('getSize')->willReturn(128); $searchResultsMock = $this->createMock(\Magento\Catalog\Api\Data\ProductSearchResultsInterface::class); $searchResultsMock->expects($this->once())->method('setSearchCriteria')->with($searchCriteriaMock); - $searchResultsMock->expects($this->once())->method('setItems')->with([$this->productMock]); - $this->searchResultsFactoryMock->expects($this->once())->method('create')->willReturn($searchResultsMock); + $searchResultsMock->expects($this->once())->method('setItems')->with([$this->product]); + $this->searchResultsFactory->expects($this->once())->method('create')->willReturn($searchResultsMock); $this->assertEquals($searchResultsMock, $this->model->getList($searchCriteriaMock)); } @@ -821,28 +842,28 @@ public function cacheKeyDataProvider() */ public function testSaveExistingWithOptions(array $newOptions, array $existingOptions, array $expectedData) { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->initializedProductMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->initializedProductMock) + ->will($this->returnValue($this->initializedProduct)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->initializedProduct) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save') - ->with($this->initializedProductMock)->willReturn(true); + $this->resourceModel->expects($this->once())->method('save') + ->with($this->initializedProduct)->willReturn(true); //option data $this->productData['options'] = $newOptions; - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->initializedProductMock->expects($this->atLeastOnce()) + $this->initializedProduct->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); - $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); + $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); - $this->assertEquals($this->initializedProductMock, $this->model->save($this->productMock)); + $this->assertEquals($this->initializedProduct, $this->model->save($this->product)); } /** @@ -992,27 +1013,27 @@ public function saveExistingWithOptionsDataProvider() */ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $expectedData) { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->resourceModel->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->initializedProductMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->initializedProductMock) + ->will($this->returnValue($this->initializedProduct)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->initializedProduct) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save') - ->with($this->initializedProductMock)->willReturn(true); + $this->resourceModel->expects($this->once())->method('save') + ->with($this->initializedProduct)->willReturn(true); - $this->initializedProductMock->setData("product_links", $existingLinks); + $this->initializedProduct->setData("product_links", $existingLinks); if (!empty($newLinks)) { $linkTypes = ['related' => 1, 'upsell' => 4, 'crosssell' => 5, 'associated' => 3]; - $this->linkTypeProviderMock->expects($this->once()) + $this->linkTypeProvider->expects($this->once()) ->method('getLinkTypes') ->willReturn($linkTypes); - $this->initializedProductMock->setData("ignore_links_flag", false); - $this->resourceModelMock + $this->initializedProduct->setData("ignore_links_flag", false); + $this->resourceModel ->expects($this->any())->method('getProductsIdsBySkus') ->willReturn([$newLinks['linked_product_sku'] => $newLinks['linked_product_sku']]); @@ -1029,29 +1050,29 @@ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $ $this->productData['product_links'] = [$inputLink]; - $this->initializedProductMock->expects($this->any()) + $this->initializedProduct->expects($this->any()) ->method('getProductLinks') ->willReturn([$inputLink]); } else { - $this->resourceModelMock + $this->resourceModel ->expects($this->any())->method('getProductsIdsBySkus') ->willReturn([]); $this->productData['product_links'] = []; - $this->initializedProductMock->setData('ignore_links_flag', true); - $this->initializedProductMock->expects($this->never()) + $this->initializedProduct->setData('ignore_links_flag', true); + $this->initializedProduct->expects($this->never()) ->method('getProductLinks') ->willReturn([]); } - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->at(0)) ->method('toNestedArray') ->will($this->returnValue($this->productData)); if (!empty($newLinks)) { - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->at(1)) ->method('toNestedArray') ->will($this->returnValue($newLinks)); @@ -1075,18 +1096,18 @@ public function testSaveWithLinks(array $newLinks, array $existingLinks, array $ } if (!empty($outputLinks)) { - $this->initializedProductMock->expects($this->once()) + $this->initializedProduct->expects($this->once()) ->method('setProductLinks') ->with($outputLinks); } else { - $this->initializedProductMock->expects($this->never()) + $this->initializedProduct->expects($this->never()) ->method('setProductLinks'); } - $this->initializedProductMock->expects($this->atLeastOnce()) + $this->initializedProduct->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); - $results = $this->model->save($this->initializedProductMock); - $this->assertEquals($this->initializedProductMock, $results); + $results = $this->model->save($this->initializedProduct); + $this->assertEquals($this->initializedProduct, $results); } /** @@ -1163,20 +1184,20 @@ public function saveWithLinksDataProvider() protected function setupProductMocksForSave() { - $this->resourceModelMock->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->resourceModel->expects($this->any())->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->initializedProductMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->initializedProductMock) + ->will($this->returnValue($this->initializedProduct)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->initializedProduct) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save') - ->with($this->initializedProductMock)->willReturn(true); + $this->resourceModel->expects($this->once())->method('save') + ->with($this->initializedProduct)->willReturn(true); } public function testSaveExistingWithNewMediaGalleryEntries() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); $newEntriesData = [ 'images' => [ [ @@ -1200,13 +1221,13 @@ public function testSaveExistingWithNewMediaGalleryEntries() $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery'] = $newEntriesData; - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->initializedProductMock->setData('media_gallery', $newEntriesData); - $this->initializedProductMock->expects($this->any()) + $this->initializedProduct->setData('media_gallery', $newEntriesData); + $this->initializedProduct->expects($this->any()) ->method('getMediaAttributes') ->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]); @@ -1215,7 +1236,7 @@ public function testSaveExistingWithNewMediaGalleryEntries() $absolutePath = '/a/b/filename.jpg'; $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); + ->with($this->initializedProduct, ['image', 'small_image']); $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) ->disableOriginalConstructor() @@ -1224,7 +1245,7 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->method('getTmpMediaShortUrl') ->with($absolutePath) ->willReturn($mediaTmpPath . $absolutePath); - $this->initializedProductMock->expects($this->once()) + $this->initializedProduct->expects($this->once()) ->method('getMediaConfig') ->willReturn($mediaConfigMock); @@ -1233,21 +1254,21 @@ public function testSaveExistingWithNewMediaGalleryEntries() ->disableOriginalConstructor() ->setMethods(null) ->getMock(); - $this->contentFactoryMock->expects($this->once()) + $this->contentFactory->expects($this->once()) ->method('create') ->willReturn($contentDataObject); - $this->imageProcessorMock->expects($this->once()) + $this->imageProcessor->expects($this->once()) ->method('processImageContent') ->willReturn($absolutePath); $imageFileUri = "imageFileUri"; $this->mediaGalleryProcessor->expects($this->once())->method('addImage') - ->with($this->initializedProductMock, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) + ->with($this->initializedProduct, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) ->willReturn($imageFileUri); $this->mediaGalleryProcessor->expects($this->once())->method('updateImage') ->with( - $this->initializedProductMock, + $this->initializedProduct, $imageFileUri, [ 'label' => 'label_text', @@ -1256,11 +1277,11 @@ public function testSaveExistingWithNewMediaGalleryEntries() 'media_type' => 'media_type', ] ); - $this->initializedProductMock->expects($this->atLeastOnce()) + $this->initializedProduct->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); - $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); + $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); - $this->model->save($this->productMock); + $this->model->save($this->product); } /** @@ -1276,38 +1297,38 @@ public function websitesProvider() public function testSaveWithDifferentWebsites() { $storeMock = $this->createMock(StoreInterface::class); - $this->resourceModelMock->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); - $this->resourceModelMock->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); - $this->productFactoryMock->expects($this->any()) + $this->resourceModel->expects($this->at(0))->method('getIdBySku')->will($this->returnValue(null)); + $this->resourceModel->expects($this->at(3))->method('getIdBySku')->will($this->returnValue(100)); + $this->productFactory->expects($this->any()) ->method('create') - ->will($this->returnValue($this->productMock)); - $this->initializationHelperMock->expects($this->never())->method('initialize'); - $this->resourceModelMock->expects($this->once())->method('validate')->with($this->productMock) + ->will($this->returnValue($this->product)); + $this->initializationHelper->expects($this->never())->method('initialize'); + $this->resourceModel->expects($this->once())->method('validate')->with($this->product) ->willReturn(true); - $this->resourceModelMock->expects($this->once())->method('save')->with($this->productMock)->willReturn(true); - $this->extensibleDataObjectConverterMock + $this->resourceModel->expects($this->once())->method('save')->with($this->product)->willReturn(true); + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->storeManagerMock->expects($this->any()) + $this->storeManager->expects($this->any()) ->method('getStore') ->willReturn($storeMock); - $this->storeManagerMock->expects($this->once()) + $this->storeManager->expects($this->once()) ->method('getWebsites') ->willReturn([ 1 => ['first'], 2 => ['second'], 3 => ['third'] ]); - $this->productMock->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]); - $this->productMock->method('getSku')->willReturn('simple'); + $this->product->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]); + $this->product->method('getSku')->willReturn('simple'); - $this->assertEquals($this->productMock, $this->model->save($this->productMock)); + $this->assertEquals($this->product, $this->model->save($this->product)); } public function testSaveExistingWithMediaGalleryEntries() { - $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); + $this->storeManager->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); //update one entry, delete one entry $newEntries = [ [ @@ -1355,26 +1376,26 @@ public function testSaveExistingWithMediaGalleryEntries() $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery']['images'] = $newEntries; - $this->extensibleDataObjectConverterMock + $this->extensibleDataObjectConverter ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->initializedProductMock->setData('media_gallery', $existingMediaGallery); - $this->initializedProductMock->expects($this->any()) + $this->initializedProduct->setData('media_gallery', $existingMediaGallery); + $this->initializedProduct->expects($this->any()) ->method('getMediaAttributes') ->willReturn(["image" => "filename1", "small_image" => "filename2"]); $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); + ->with($this->initializedProduct, ['image', 'small_image']); $this->mediaGalleryProcessor->expects($this->once()) ->method('setMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image'], 'filename1'); - $this->initializedProductMock->expects($this->atLeastOnce()) + ->with($this->initializedProduct, ['image', 'small_image'], 'filename1'); + $this->initializedProduct->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); - $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); - $this->productMock->expects($this->any())->method('getMediaGalleryEntries')->willReturn(null); - $this->model->save($this->productMock); - $this->assertEquals($expectedResult, $this->initializedProductMock->getMediaGallery('images')); + $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); + $this->product->expects($this->any())->method('getMediaGalleryEntries')->willReturn(null); + $this->model->save($this->product); + $this->assertEquals($expectedResult, $this->initializedProduct->getMediaGallery('images')); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Layer/Filter/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Layer/Filter/PriceTest.php deleted file mode 100644 index 9fba7d833c25a..0000000000000 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Layer/Filter/PriceTest.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Layer\Filter; - -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; - -class PriceTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Catalog\Model\ResourceModel\Layer\Filter\Price - */ - private $model; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $resourceMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManager($this); - - $contextMock = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\Context::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) - ->disableOriginalConstructor() - ->getMock(); - $contextMock->expects($this->once())->method('getResources')->willReturn($this->resourceMock); - $this->model = $objectManagerHelper->getObject( - \Magento\Catalog\Model\ResourceModel\Layer\Filter\Price::class, - [ - 'context' => $contextMock - ] - ); - } - - public function testGetMainTable() - { - $expectedTableName = 'expectedTableName'; - $this->resourceMock->expects($this->once())->method('getTableName')->willReturn($expectedTableName); - $this->assertEquals($expectedTableName, $this->model->getMainTable()); - } -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index c73e772de3702..dbbb3fb29513b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\Framework\DB\Select; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -239,7 +240,7 @@ public function testAddMediaGalleryData() $mediaGalleriesMock = [[$linkField => $rowId]]; $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() - ->setMethods(['getData']) + ->setMethods(['getOrigData']) ->getMock(); $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) ->disableOriginalConstructor() @@ -254,8 +255,10 @@ public function testAddMediaGalleryData() $this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock); $attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId); $this->entityMock->expects($this->once())->method('getAttribute')->willReturn($attributeMock); - $itemMock->expects($this->atLeastOnce())->method('getData')->willReturn($rowId); - $selectMock->expects($this->once())->method('where')->with('entity.' . $linkField . ' IN (?)', [$rowId]); + $itemMock->expects($this->atLeastOnce())->method('getOrigData')->willReturn($rowId); + $selectMock->expects($this->once())->method('reset')->with(Select::ORDER)->willReturnSelf(); + $selectMock->expects($this->once())->method('where')->with('entity.' . $linkField . ' IN (?)', [$rowId]) + ->willReturnSelf(); $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadataMock); $metadataMock->expects($this->once())->method('getLinkField')->willReturn($linkField); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php index cec862ee9661f..6f3d8e1a84b17 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php @@ -7,6 +7,9 @@ use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class LinkedProductSelectBuilderByIndexPriceTest extends \PHPUnit\Framework\TestCase { /** @@ -56,12 +59,26 @@ protected function setUp() $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + + $this->indexScopeResolverMock = $this->createMock( + \Magento\Framework\Search\Request\IndexScopeResolverInterface::class + ); + $this->dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + $this->dimensionFactoryMock = $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class); + $this->dimensionFactoryMock->method('create')->willReturn($this->dimensionMock); + $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $storeMock->method('getId')->willReturn(1); + $storeMock->method('getWebsiteId')->willReturn(1); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + $this->model = new \Magento\Catalog\Model\ResourceModel\Product\Indexer\LinkedProductSelectBuilderByIndexPrice( $this->storeManagerMock, $this->resourceMock, $this->customerSessionMock, $this->metadataPoolMock, - $this->baseSelectProcessorMock + $this->baseSelectProcessorMock, + $this->indexScopeResolverMock, + $this->dimensionFactoryMock ); } @@ -79,7 +96,7 @@ public function testBuild() $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) ->getMockForAbstractClass(); $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); - $this->customerSessionMock->expects($this->once())->method('getCustomerGroupId'); + $this->customerSessionMock->expects($this->once())->method('getCustomerGroupId')->willReturn(1); $connection->expects($this->any())->method('select')->willReturn($select); $select->expects($this->any())->method('from')->willReturnSelf(); $select->expects($this->any())->method('joinInner')->willReturnSelf(); diff --git a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php index 316e08a56f99c..dbf1292e57368 100644 --- a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php @@ -41,7 +41,7 @@ class BreadcrumbsTest extends \PHPUnit\Framework\TestCase /** * @inheritdoc */ - protected function setUp() + protected function setUp() : void { $this->catalogHelper = $this->getMockBuilder(CatalogHelper::class) ->setMethods(['getProduct']) @@ -53,11 +53,14 @@ protected function setUp() ->disableOriginalConstructor() ->getMockForAbstractClass(); + $escaper = $this->getObjectManager()->getObject(\Magento\Framework\Escaper::class); + $this->viewModel = $this->getObjectManager()->getObject( Breadcrumbs::class, [ 'catalogData' => $this->catalogHelper, 'scopeConfig' => $this->scopeConfig, + 'escaper' => $escaper ] ); } @@ -65,7 +68,7 @@ protected function setUp() /** * @return void */ - public function testGetCategoryUrlSuffix() + public function testGetCategoryUrlSuffix() : void { $this->scopeConfig->expects($this->once()) ->method('getValue') @@ -78,7 +81,7 @@ public function testGetCategoryUrlSuffix() /** * @return void */ - public function testIsCategoryUsedInProductUrl() + public function testIsCategoryUsedInProductUrl() : void { $this->scopeConfig->expects($this->once()) ->method('isSetFlag') @@ -95,7 +98,7 @@ public function testIsCategoryUsedInProductUrl() * @param string $expectedName * @return void */ - public function testGetProductName($product, string $expectedName) + public function testGetProductName($product, string $expectedName) : void { $this->catalogHelper->expects($this->atLeastOnce()) ->method('getProduct') @@ -107,7 +110,7 @@ public function testGetProductName($product, string $expectedName) /** * @return array */ - public function productDataProvider() + public function productDataProvider() : array { return [ [$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test']]), 'Test'], @@ -115,10 +118,63 @@ public function productDataProvider() ]; } + /** + * @dataProvider productJsonEncodeDataProvider + * + * @param Product|null $product + * @param string $expectedJson + * @return void + */ + public function testGetJsonConfiguration($product, string $expectedJson) : void + { + $this->catalogHelper->expects($this->atLeastOnce()) + ->method('getProduct') + ->willReturn($product); + + $this->scopeConfig->expects($this->any()) + ->method('isSetFlag') + ->with('catalog/seo/product_use_categories', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + ->willReturn(false); + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->with('catalog/seo/category_url_suffix', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + ->willReturn('."html'); + + $this->assertEquals($expectedJson, $this->viewModel->getJsonConfiguration()); + } + + /** + * @return array + */ + public function productJsonEncodeDataProvider() : array + { + return [ + [ + $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test ™']]), + '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test \u2122"}}', + ], + [ + $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test "']]), + '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test ""}}', + ], + [ + $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test <b>x</b>']]), + '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":' + . '"Test <b>x<\/b>"}}', + ], + [ + $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test \'abc\'']]), + '{"breadcrumbs":' + . '{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test 'abc'"}}' + ], + ]; + } + /** * @return ObjectManager */ - private function getObjectManager() + private function getObjectManager() : ObjectManager { if (null === $this->objectManager) { $this->objectManager = new ObjectManager($this); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php index 7196a721f1d02..86f1db2022cc9 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php @@ -166,7 +166,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyData(array $data) @@ -226,7 +226,7 @@ protected function formatPriceByPath($path, array $data) } /** - * {@inheritdoc} + * @inheritdoc * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -923,7 +923,7 @@ protected function getPriceFieldConfig($sortOrder) 'addbeforePool' => $this->productOptionsPrice->prefixesToOptionArray(), 'sortOrder' => $sortOrder, 'validation' => [ - 'validate-zero-or-greater' => true + 'validate-number' => true ], ], ], diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index 7cd81419c0347..e425e1c732d75 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -287,7 +287,7 @@ public function modifyMeta(array $meta) if ($attributes) { $meta[$groupCode]['children'] = $this->getAttributesMeta($attributes, $groupCode); $meta[$groupCode]['arguments']['data']['config']['componentType'] = Fieldset::NAME; - $meta[$groupCode]['arguments']['data']['config']['label'] = __('%1', $group->getAttributeGroupName()); + $meta[$groupCode]['arguments']['data']['config']['label'] = __($group->getAttributeGroupName()); $meta[$groupCode]['arguments']['data']['config']['collapsible'] = true; $meta[$groupCode]['arguments']['data']['config']['dataScope'] = self::DATA_SCOPE_PRODUCT; $meta[$groupCode]['arguments']['data']['config']['sortOrder'] = diff --git a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php index 01c286cdc74ce..95f2531e5fdca 100644 --- a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php +++ b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types=1); namespace Magento\Catalog\ViewModel\Product; @@ -32,11 +31,6 @@ class Breadcrumbs extends DataObject implements ArgumentInterface */ private $scopeConfig; - /** - * @var Json - */ - private $json; - /** * @var Escaper */ @@ -47,6 +41,7 @@ class Breadcrumbs extends DataObject implements ArgumentInterface * @param ScopeConfigInterface $scopeConfig * @param Json|null $json * @param Escaper|null $escaper + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Data $catalogData, @@ -58,7 +53,6 @@ public function __construct( $this->catalogData = $catalogData; $this->scopeConfig = $scopeConfig; - $this->json = $json ?: ObjectManager::getInstance()->get(Json::class); $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); } @@ -101,20 +95,32 @@ public function getProductName(): string } /** - * Returns breadcrumb json. + * Returns breadcrumb json with html escaped names * * @return string */ - public function getJsonConfiguration() + public function getJsonConfigurationHtmlEscaped() : string { - return $this->json->serialize( + return json_encode( [ 'breadcrumbs' => [ 'categoryUrlSuffix' => $this->escaper->escapeHtml($this->getCategoryUrlSuffix()), 'userCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(), - 'product' => $this->escaper->escapeHtml($this->escaper->escapeJs($this->getProductName())) + 'product' => $this->escaper->escapeHtml($this->getProductName()) ] - ] + ], + JSON_HEX_TAG ); } + + /** + * Returns breadcrumb json. + * + * @return string + * @deprecated in favor of new method with name {suffix}Html{postfix}() + */ + public function getJsonConfiguration() + { + return $this->getJsonConfigurationHtmlEscaped(); + } } diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index e6dbb10e811b4..71a799fd22427 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -92,18 +92,12 @@ <comment>Whether to show "All" option in the "Show X Per Page" dropdown</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> - <field id="parse_url_directives" translate="label comment" type="select" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> - <label>Allow Dynamic Media URLs</label> - <comment>E.g. {{media url="path/to/image.jpg"}} {{skin url="path/to/picture.gif"}}. Dynamic directives parsing impacts catalog performance.</comment> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> </group> <group id="placeholder" translate="label" sortOrder="300" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Product Image Placeholders</label> <clone_fields>1</clone_fields> <clone_model>Magento\Catalog\Model\Config\CatalogClone\Media\Image</clone_model> <field id="placeholder" type="image" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> - <label></label> <backend_model>Magento\Config\Model\Config\Backend\Image</backend_model> <upload_dir config="system/filesystem/media" scope_info="1">catalog/product/placeholder</upload_dir> <base_url type="media" scope_info="1">catalog/product/placeholder</base_url> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 1d92197e390a1..f52760aa50743 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -52,6 +52,11 @@ <forbidden_extensions>php,exe</forbidden_extensions> </custom_options> </catalog> + <indexer> + <catalog_product_price> + <dimensions_mode>none</dimensions_mode> + </catalog_product_price> + </indexer> <system> <media_storage_configuration> <allowed_resources> diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index 60789c016ff0f..7b4e1a96adda0 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -1199,6 +1199,8 @@ comment="Catalog Product Website Index Table"> <column xsi:type="smallint" name="website_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Website ID"/> + <column xsi:type="smallint" name="default_store_id" padding="5" unsigned="true" nullable="false" identity="false" + comment="Default store id for website"/> <column xsi:type="date" name="website_date" comment="Website Date"/> <column xsi:type="float" name="rate" unsigned="false" nullable="true" default="1" comment="Rate"/> <constraint xsi:type="primary" name="PRIMARY"> diff --git a/app/code/Magento/Catalog/etc/db_schema_whitelist.json b/app/code/Magento/Catalog/etc/db_schema_whitelist.json index 1c2c660ca9b00..b1543a6a007f9 100644 --- a/app/code/Magento/Catalog/etc/db_schema_whitelist.json +++ b/app/code/Magento/Catalog/etc/db_schema_whitelist.json @@ -1,1123 +1,1124 @@ { - "catalog_product_entity": { - "column": { - "entity_id": true, - "attribute_set_id": true, - "type_id": true, - "sku": true, - "has_options": true, - "required_options": true, - "created_at": true, - "updated_at": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID": true, - "CATALOG_PRODUCT_ENTITY_SKU": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_ATTR_SET_ID_EAV_ATTR_SET_ATTR_SET_ID": true - } - }, - "catalog_product_entity_datetime": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_DATETIME_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_DTIME_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_product_entity_decimal": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_DECIMAL_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_DECIMAL_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_DEC_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_product_entity_int": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_INT_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_INT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_INT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_product_entity_text": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_TEXT_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_TEXT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_TEXT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_product_entity_varchar": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_VARCHAR_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_VARCHAR_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_VCHR_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_product_entity_gallery": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "position": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_GALLERY_ENTITY_ID": true, - "CATALOG_PRODUCT_ENTITY_GALLERY_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_GALLERY_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_GLR_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_PRD_ENTT_GLR_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_ENTITY_GALLERY_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_GALLERY_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_entity": { - "column": { - "entity_id": true, - "attribute_set_id": true, - "parent_id": true, - "created_at": true, - "updated_at": true, - "path": true, - "position": true, - "level": true, - "children_count": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_LEVEL": true, - "CATALOG_CATEGORY_ENTITY_PATH": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_category_entity_datetime": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_DATETIME_ENTITY_ID": true, - "CATALOG_CATEGORY_ENTITY_DATETIME_ATTRIBUTE_ID": true, - "CATALOG_CATEGORY_ENTITY_DATETIME_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_CTGR_ENTT_DTIME_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, - "CATALOG_CATEGORY_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_entity_decimal": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_DECIMAL_ENTITY_ID": true, - "CATALOG_CATEGORY_ENTITY_DECIMAL_ATTRIBUTE_ID": true, - "CATALOG_CATEGORY_ENTITY_DECIMAL_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_CTGR_ENTT_DEC_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, - "CATALOG_CATEGORY_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_entity_int": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_INT_ENTITY_ID": true, - "CATALOG_CATEGORY_ENTITY_INT_ATTRIBUTE_ID": true, - "CATALOG_CATEGORY_ENTITY_INT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_CTGR_ENTT_INT_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, - "CATALOG_CATEGORY_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_entity_text": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_TEXT_ENTITY_ID": true, - "CATALOG_CATEGORY_ENTITY_TEXT_ATTRIBUTE_ID": true, - "CATALOG_CATEGORY_ENTITY_TEXT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_CTGR_ENTT_TEXT_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, - "CATALOG_CATEGORY_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_entity_varchar": { - "column": { - "value_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CATALOG_CATEGORY_ENTITY_VARCHAR_ENTITY_ID": true, - "CATALOG_CATEGORY_ENTITY_VARCHAR_ATTRIBUTE_ID": true, - "CATALOG_CATEGORY_ENTITY_VARCHAR_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CAT_CTGR_ENTT_VCHR_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, - "CATALOG_CATEGORY_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "catalog_category_product": { - "column": { - "entity_id": true, - "category_id": true, - "product_id": true, - "position": true - }, - "index": { - "CATALOG_CATEGORY_PRODUCT_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_CTGR_PRD_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_CTGR_PRD_CTGR_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CATALOG_CATEGORY_PRODUCT_CATEGORY_ID_PRODUCT_ID": true, - "CAT_CTGR_PRD_CTGR_ID_SEQUENCE_CAT_CTGR_SEQUENCE_VAL": true - } - }, - "catalog_category_product_index": { - "column": { - "category_id": true, - "product_id": true, - "position": true, - "is_parent": true, - "store_id": true, - "visibility": true - }, - "index": { - "CAT_CTGR_PRD_IDX_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, - "CAT_CTGR_PRD_IDX_STORE_ID_CTGR_ID_VISIBILITY_IS_PARENT_POSITION": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_compare_item": { - "column": { - "catalog_compare_item_id": true, - "visitor_id": true, - "customer_id": true, - "product_id": true, - "store_id": true - }, - "index": { - "CATALOG_COMPARE_ITEM_PRODUCT_ID": true, - "CATALOG_COMPARE_ITEM_VISITOR_ID_PRODUCT_ID": true, - "CATALOG_COMPARE_ITEM_CUSTOMER_ID_PRODUCT_ID": true, - "CATALOG_COMPARE_ITEM_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATALOG_COMPARE_ITEM_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CATALOG_COMPARE_ITEM_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "CATALOG_COMPARE_ITEM_STORE_ID_STORE_STORE_ID": true - } - }, - "catalog_product_website": { - "column": { - "product_id": true, - "website_id": true - }, - "index": { - "CATALOG_PRODUCT_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATALOG_PRODUCT_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "CAT_PRD_WS_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true - } - }, - "catalog_product_link_type": { - "column": { - "link_type_id": true, - "code": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_link": { - "column": { - "link_id": true, - "product_id": true, - "linked_product_id": true, - "link_type_id": true - }, - "index": { - "CATALOG_PRODUCT_LINK_PRODUCT_ID": true, - "CATALOG_PRODUCT_LINK_LINKED_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_LNK_LNKED_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_LINK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "CAT_PRD_LNK_LNK_TYPE_ID_CAT_PRD_LNK_TYPE_LNK_TYPE_ID": true, - "CATALOG_PRODUCT_LINK_LINK_TYPE_ID_PRODUCT_ID_LINKED_PRODUCT_ID": true - } - }, - "catalog_product_link_attribute": { - "column": { - "product_link_attribute_id": true, - "link_type_id": true, - "product_link_attribute_code": true, - "data_type": true - }, - "index": { - "CATALOG_PRODUCT_LINK_ATTRIBUTE_LINK_TYPE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_LNK_ATTR_LNK_TYPE_ID_CAT_PRD_LNK_TYPE_LNK_TYPE_ID": true - } - }, - "catalog_product_link_attribute_decimal": { - "column": { - "value_id": true, - "product_link_attribute_id": true, - "link_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_LINK_ATTRIBUTE_DECIMAL_LINK_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_LNK_ATTR_DEC_LNK_ID_CAT_PRD_LNK_LNK_ID": true, - "FK_AB2EFA9A14F7BCF1D5400056203D14B6": true, - "CAT_PRD_LNK_ATTR_DEC_PRD_LNK_ATTR_ID_LNK_ID": true - } - }, - "catalog_product_link_attribute_int": { - "column": { - "value_id": true, - "product_link_attribute_id": true, - "link_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_LINK_ATTRIBUTE_INT_LINK_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_LNK_ATTR_INT_LNK_ID_CAT_PRD_LNK_LNK_ID": true, - "FK_D6D878F8BA2A4282F8DDED7E6E3DE35C": true, - "CAT_PRD_LNK_ATTR_INT_PRD_LNK_ATTR_ID_LNK_ID": true - } - }, - "catalog_product_link_attribute_varchar": { - "column": { - "value_id": true, - "product_link_attribute_id": true, - "link_id": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_LINK_ATTRIBUTE_VARCHAR_LINK_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_LNK_ATTR_VCHR_LNK_ID_CAT_PRD_LNK_LNK_ID": true, - "FK_DEE9C4DA61CFCC01DFCF50F0D79CEA51": true, - "CAT_PRD_LNK_ATTR_VCHR_PRD_LNK_ATTR_ID_LNK_ID": true - } - }, - "catalog_product_entity_tier_price": { - "column": { - "value_id": true, - "entity_id": true, - "all_groups": true, - "customer_group_id": true, - "qty": true, - "value": true, - "website_id": true, - "percentage_value": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_TIER_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_ENTITY_TIER_PRICE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_TIER_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "CAT_PRD_ENTT_TIER_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_ENTT_TIER_PRICE_WS_ID_STORE_WS_WS_ID": true, - "UNQ_E8AB433B9ACB00343ABB312AD2FAB087": true - } - }, - "catalog_product_entity_media_gallery": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true, - "media_type": true, - "disabled": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_ENTITY_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_MDA_GLR_ATTR_ID_EAV_ATTR_ATTR_ID": true - } - }, - "catalog_product_entity_media_gallery_value": { - "column": { - "value_id": true, - "store_id": true, - "entity_id": true, - "label": true, - "position": true, - "disabled": true, - "record_id": true - }, - "index": { - "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_STORE_ID": true, - "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_ENTITY_ID": true, - "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_VALUE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_VAL_ID_CAT_PRD_ENTT_MDA_GLR_VAL_ID": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_STORE_ID_STORE_STORE_ID": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true - } - }, - "catalog_product_option": { - "column": { - "option_id": true, - "product_id": true, - "type": true, - "is_require": true, - "sku": true, - "max_characters": true, - "file_extension": true, - "image_size_x": true, - "image_size_y": true, - "sort_order": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_OPT_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true - } - }, - "catalog_product_option_price": { - "column": { - "option_price_id": true, - "option_id": true, - "store_id": true, - "price": true, - "price_type": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_PRICE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_OPT_PRICE_OPT_ID_CAT_PRD_OPT_OPT_ID": true, - "CATALOG_PRODUCT_OPTION_PRICE_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_OPTION_PRICE_OPTION_ID_STORE_ID": true - } - }, - "catalog_product_option_title": { - "column": { - "option_title_id": true, - "option_id": true, - "store_id": true, - "title": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_OPT_TTL_OPT_ID_CAT_PRD_OPT_OPT_ID": true, - "CATALOG_PRODUCT_OPTION_TITLE_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_OPTION_TITLE_OPTION_ID_STORE_ID": true - } - }, - "catalog_product_option_type_value": { - "column": { - "option_type_id": true, - "option_id": true, - "sku": true, - "sort_order": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_TYPE_VALUE_OPTION_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_OPT_TYPE_VAL_OPT_ID_CAT_PRD_OPT_OPT_ID": true - } - }, - "catalog_product_option_type_price": { - "column": { - "option_type_price_id": true, - "option_type_id": true, - "store_id": true, - "price": true, - "price_type": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "FK_B523E3378E8602F376CC415825576B7F": true, - "CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_OPTION_TYPE_PRICE_OPTION_TYPE_ID_STORE_ID": true - } - }, - "catalog_product_option_type_title": { - "column": { - "option_type_title_id": true, - "option_type_id": true, - "store_id": true, - "title": true - }, - "index": { - "CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "FK_C085B9CF2C2A302E8043FDEA1937D6A2": true, - "CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE_ID_STORE_STORE_ID": true, - "CATALOG_PRODUCT_OPTION_TYPE_TITLE_OPTION_TYPE_ID_STORE_ID": true - } - }, - "catalog_eav_attribute": { - "column": { - "attribute_id": true, - "frontend_input_renderer": true, - "is_global": true, - "is_visible": true, - "is_searchable": true, - "is_filterable": true, - "is_comparable": true, - "is_visible_on_front": true, - "is_html_allowed_on_front": true, - "is_used_for_price_rules": true, - "is_filterable_in_search": true, - "used_in_product_listing": true, - "used_for_sort_by": true, - "apply_to": true, - "is_visible_in_advanced_search": true, - "position": true, - "is_wysiwyg_enabled": true, - "is_used_for_promo_rules": true, - "is_required_in_admin_store": true, - "is_used_in_grid": true, - "is_visible_in_grid": true, - "is_filterable_in_grid": true - }, - "index": { - "CATALOG_EAV_ATTRIBUTE_USED_FOR_SORT_BY": true, - "CATALOG_EAV_ATTRIBUTE_USED_IN_PRODUCT_LISTING": true - }, - "constraint": { - "PRIMARY": true, - "CATALOG_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true - } - }, - "catalog_product_relation": { - "column": { - "parent_id": true, - "child_id": true - }, - "index": { - "CATALOG_PRODUCT_RELATION_CHILD_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_RELATION_CHILD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_RELATION_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true - } - }, - "catalog_product_index_eav": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_eav_decimal": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_DEC_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_price": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price": true, - "final_price": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_MIN_PRICE": true, - "CAT_PRD_IDX_PRICE_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "CAT_PRD_IDX_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "catalog_product_index_tier_price": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_TIER_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_TIER_PRICE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_TIER_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "CAT_PRD_IDX_TIER_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_IDX_TIER_PRICE_WS_ID_STORE_WS_WS_ID": true - } - }, - "catalog_product_index_website": { - "column": { - "website_id": true, - "website_date": true, - "rate": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_WEBSITE_WEBSITE_DATE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_WS_WS_ID_STORE_WS_WS_ID": true - } - }, - "catalog_product_index_price_cfg_opt_agr_idx": { - "column": { - "parent_id": true, - "child_id": true, - "customer_group_id": true, - "website_id": true, - "price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_cfg_opt_agr_tmp": { - "column": { - "parent_id": true, - "child_id": true, - "customer_group_id": true, - "website_id": true, - "price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_cfg_opt_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_cfg_opt_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_final_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "orig_price": true, - "price": true, - "min_price": true, - "max_price": true, - "tier_price": true, - "base_tier": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_final_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "orig_price": true, - "price": true, - "min_price": true, - "max_price": true, - "tier_price": true, - "base_tier": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_opt_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_opt_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_opt_agr_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_opt_agr_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "option_id": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_eav_idx": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_IDX_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_IDX_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_IDX_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_IDX_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_eav_tmp": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_TMP_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_TMP_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_TMP_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_TMP_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_eav_decimal_idx": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_DEC_IDX_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_eav_decimal_tmp": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_IDX_EAV_DEC_TMP_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true - } - }, - "catalog_product_index_price_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price": true, - "final_price": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_PRICE_IDX_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_IDX_WEBSITE_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_IDX_MIN_PRICE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price": true, - "final_price": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_PRICE_TMP_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_TMP_WEBSITE_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_category_product_index_tmp": { - "column": { - "category_id": true, - "product_id": true, - "position": true, - "is_parent": true, - "store_id": true, - "visibility": true - }, - "constraint": { - "PRIMARY": true - }, - "index": { - "CAT_CTGR_PRD_IDX_TMP_PRD_ID_CTGR_ID_STORE_ID": true - } - }, - "catalog_product_entity_media_gallery_value_to_entity": { - "column": { - "value_id": true, - "entity_id": true - }, - "constraint": { - "FK_A6C6C8FAA386736921D3A7C4B50B1185": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_VAL_ID_ENTT_ID": true - } - }, - "catalog_product_index_eav_replica": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_REPLICA_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_REPLICA_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_REPLICA_VALUE": true, - "CATALOG_PRODUCT_INDEX_EAV_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_VALUE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_eav_decimal_replica": { - "column": { - "entity_id": true, - "attribute_id": true, - "store_id": true, - "value": true, - "source_id": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_VALUE": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_ATTRIBUTE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_STORE_ID": true, - "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_VALUE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_replica": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "tax_class_id": true, - "price": true, - "final_price": true, - "min_price": true, - "max_price": true, - "tier_price": true - }, - "index": { - "CATALOG_PRODUCT_INDEX_PRICE_REPLICA_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_REPLICA_MIN_PRICE": true, - "CAT_PRD_IDX_PRICE_REPLICA_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true, - "CATALOG_PRODUCT_INDEX_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOG_PRODUCT_INDEX_PRICE_MIN_PRICE": true, - "CAT_PRD_IDX_PRICE_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_category_product_index_replica": { - "column": { - "category_id": true, - "product_id": true, - "position": true, - "is_parent": true, - "store_id": true, - "visibility": true - }, - "index": { - "CAT_CTGR_PRD_IDX_REPLICA_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, - "IDX_87EB2E3059853CF89A75B4C55074810B": true, - "CAT_CTGR_PRD_IDX_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, - "CAT_CTGR_PRD_IDX_STORE_ID_CTGR_ID_VISIBILITY_IS_PARENT_POSITION": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_frontend_action": { - "column": { - "action_id": true, - "type_id": true, - "visitor_id": true, - "customer_id": true, - "product_id": true, - "added_at": true - }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_FRONTEND_ACTION_CSTR_ID_CSTR_ENTT_ENTT_ID": true, - "CAT_PRD_FRONTEND_ACTION_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_FRONTEND_ACTION_VISITOR_ID_PRODUCT_ID_TYPE_ID": true, - "CATALOG_PRODUCT_FRONTEND_ACTION_CUSTOMER_ID_PRODUCT_ID_TYPE_ID": true + "catalog_product_entity": { + "column": { + "entity_id": true, + "attribute_set_id": true, + "type_id": true, + "sku": true, + "has_options": true, + "required_options": true, + "created_at": true, + "updated_at": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_ATTRIBUTE_SET_ID": true, + "CATALOG_PRODUCT_ENTITY_SKU": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_ATTR_SET_ID_EAV_ATTR_SET_ATTR_SET_ID": true + } + }, + "catalog_product_entity_datetime": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_DATETIME_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_DTIME_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_product_entity_decimal": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_DECIMAL_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_DECIMAL_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_DEC_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_product_entity_int": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_INT_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_INT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_INT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_product_entity_text": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_TEXT_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_TEXT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_TEXT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_product_entity_varchar": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_VARCHAR_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_VARCHAR_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_VCHR_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_product_entity_gallery": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "position": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_GALLERY_ENTITY_ID": true, + "CATALOG_PRODUCT_ENTITY_GALLERY_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_GALLERY_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_GLR_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_PRD_ENTT_GLR_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_ENTITY_GALLERY_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_GALLERY_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_entity": { + "column": { + "entity_id": true, + "attribute_set_id": true, + "parent_id": true, + "created_at": true, + "updated_at": true, + "path": true, + "position": true, + "level": true, + "children_count": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_LEVEL": true, + "CATALOG_CATEGORY_ENTITY_PATH": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_category_entity_datetime": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_DATETIME_ENTITY_ID": true, + "CATALOG_CATEGORY_ENTITY_DATETIME_ATTRIBUTE_ID": true, + "CATALOG_CATEGORY_ENTITY_DATETIME_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_CTGR_ENTT_DTIME_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, + "CATALOG_CATEGORY_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_entity_decimal": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_DECIMAL_ENTITY_ID": true, + "CATALOG_CATEGORY_ENTITY_DECIMAL_ATTRIBUTE_ID": true, + "CATALOG_CATEGORY_ENTITY_DECIMAL_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_CTGR_ENTT_DEC_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, + "CATALOG_CATEGORY_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_entity_int": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_INT_ENTITY_ID": true, + "CATALOG_CATEGORY_ENTITY_INT_ATTRIBUTE_ID": true, + "CATALOG_CATEGORY_ENTITY_INT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_CTGR_ENTT_INT_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, + "CATALOG_CATEGORY_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_entity_text": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_TEXT_ENTITY_ID": true, + "CATALOG_CATEGORY_ENTITY_TEXT_ATTRIBUTE_ID": true, + "CATALOG_CATEGORY_ENTITY_TEXT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_CTGR_ENTT_TEXT_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, + "CATALOG_CATEGORY_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_entity_varchar": { + "column": { + "value_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CATALOG_CATEGORY_ENTITY_VARCHAR_ENTITY_ID": true, + "CATALOG_CATEGORY_ENTITY_VARCHAR_ATTRIBUTE_ID": true, + "CATALOG_CATEGORY_ENTITY_VARCHAR_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CAT_CTGR_ENTT_VCHR_ENTT_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, + "CATALOG_CATEGORY_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "catalog_category_product": { + "column": { + "entity_id": true, + "category_id": true, + "product_id": true, + "position": true + }, + "index": { + "CATALOG_CATEGORY_PRODUCT_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_CTGR_PRD_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_CTGR_PRD_CTGR_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CATALOG_CATEGORY_PRODUCT_CATEGORY_ID_PRODUCT_ID": true, + "CAT_CTGR_PRD_CTGR_ID_SEQUENCE_CAT_CTGR_SEQUENCE_VAL": true + } + }, + "catalog_category_product_index": { + "column": { + "category_id": true, + "product_id": true, + "position": true, + "is_parent": true, + "store_id": true, + "visibility": true + }, + "index": { + "CAT_CTGR_PRD_IDX_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, + "CAT_CTGR_PRD_IDX_STORE_ID_CTGR_ID_VISIBILITY_IS_PARENT_POSITION": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_compare_item": { + "column": { + "catalog_compare_item_id": true, + "visitor_id": true, + "customer_id": true, + "product_id": true, + "store_id": true + }, + "index": { + "CATALOG_COMPARE_ITEM_PRODUCT_ID": true, + "CATALOG_COMPARE_ITEM_VISITOR_ID_PRODUCT_ID": true, + "CATALOG_COMPARE_ITEM_CUSTOMER_ID_PRODUCT_ID": true, + "CATALOG_COMPARE_ITEM_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATALOG_COMPARE_ITEM_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CATALOG_COMPARE_ITEM_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "CATALOG_COMPARE_ITEM_STORE_ID_STORE_STORE_ID": true + } + }, + "catalog_product_website": { + "column": { + "product_id": true, + "website_id": true + }, + "index": { + "CATALOG_PRODUCT_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATALOG_PRODUCT_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "CAT_PRD_WS_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true + } + }, + "catalog_product_link_type": { + "column": { + "link_type_id": true, + "code": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_link": { + "column": { + "link_id": true, + "product_id": true, + "linked_product_id": true, + "link_type_id": true + }, + "index": { + "CATALOG_PRODUCT_LINK_PRODUCT_ID": true, + "CATALOG_PRODUCT_LINK_LINKED_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_LNK_LNKED_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_LINK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "CAT_PRD_LNK_LNK_TYPE_ID_CAT_PRD_LNK_TYPE_LNK_TYPE_ID": true, + "CATALOG_PRODUCT_LINK_LINK_TYPE_ID_PRODUCT_ID_LINKED_PRODUCT_ID": true + } + }, + "catalog_product_link_attribute": { + "column": { + "product_link_attribute_id": true, + "link_type_id": true, + "product_link_attribute_code": true, + "data_type": true + }, + "index": { + "CATALOG_PRODUCT_LINK_ATTRIBUTE_LINK_TYPE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_LNK_ATTR_LNK_TYPE_ID_CAT_PRD_LNK_TYPE_LNK_TYPE_ID": true + } + }, + "catalog_product_link_attribute_decimal": { + "column": { + "value_id": true, + "product_link_attribute_id": true, + "link_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_LINK_ATTRIBUTE_DECIMAL_LINK_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_LNK_ATTR_DEC_LNK_ID_CAT_PRD_LNK_LNK_ID": true, + "FK_AB2EFA9A14F7BCF1D5400056203D14B6": true, + "CAT_PRD_LNK_ATTR_DEC_PRD_LNK_ATTR_ID_LNK_ID": true + } + }, + "catalog_product_link_attribute_int": { + "column": { + "value_id": true, + "product_link_attribute_id": true, + "link_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_LINK_ATTRIBUTE_INT_LINK_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_LNK_ATTR_INT_LNK_ID_CAT_PRD_LNK_LNK_ID": true, + "FK_D6D878F8BA2A4282F8DDED7E6E3DE35C": true, + "CAT_PRD_LNK_ATTR_INT_PRD_LNK_ATTR_ID_LNK_ID": true + } + }, + "catalog_product_link_attribute_varchar": { + "column": { + "value_id": true, + "product_link_attribute_id": true, + "link_id": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_LINK_ATTRIBUTE_VARCHAR_LINK_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_LNK_ATTR_VCHR_LNK_ID_CAT_PRD_LNK_LNK_ID": true, + "FK_DEE9C4DA61CFCC01DFCF50F0D79CEA51": true, + "CAT_PRD_LNK_ATTR_VCHR_PRD_LNK_ATTR_ID_LNK_ID": true + } + }, + "catalog_product_entity_tier_price": { + "column": { + "value_id": true, + "entity_id": true, + "all_groups": true, + "customer_group_id": true, + "qty": true, + "value": true, + "website_id": true, + "percentage_value": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_TIER_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_ENTITY_TIER_PRICE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_TIER_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "CAT_PRD_ENTT_TIER_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_ENTT_TIER_PRICE_WS_ID_STORE_WS_WS_ID": true, + "UNQ_E8AB433B9ACB00343ABB312AD2FAB087": true + } + }, + "catalog_product_entity_media_gallery": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true, + "media_type": true, + "disabled": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_ENTITY_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_MDA_GLR_ATTR_ID_EAV_ATTR_ATTR_ID": true + } + }, + "catalog_product_entity_media_gallery_value": { + "column": { + "value_id": true, + "store_id": true, + "entity_id": true, + "label": true, + "position": true, + "disabled": true, + "record_id": true + }, + "index": { + "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_STORE_ID": true, + "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_ENTITY_ID": true, + "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_VALUE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_VAL_ID_CAT_PRD_ENTT_MDA_GLR_VAL_ID": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_STORE_ID_STORE_STORE_ID": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true + } + }, + "catalog_product_option": { + "column": { + "option_id": true, + "product_id": true, + "type": true, + "is_require": true, + "sku": true, + "max_characters": true, + "file_extension": true, + "image_size_x": true, + "image_size_y": true, + "sort_order": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_OPT_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true + } + }, + "catalog_product_option_price": { + "column": { + "option_price_id": true, + "option_id": true, + "store_id": true, + "price": true, + "price_type": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_PRICE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_OPT_PRICE_OPT_ID_CAT_PRD_OPT_OPT_ID": true, + "CATALOG_PRODUCT_OPTION_PRICE_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_OPTION_PRICE_OPTION_ID_STORE_ID": true + } + }, + "catalog_product_option_title": { + "column": { + "option_title_id": true, + "option_id": true, + "store_id": true, + "title": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_OPT_TTL_OPT_ID_CAT_PRD_OPT_OPT_ID": true, + "CATALOG_PRODUCT_OPTION_TITLE_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_OPTION_TITLE_OPTION_ID_STORE_ID": true + } + }, + "catalog_product_option_type_value": { + "column": { + "option_type_id": true, + "option_id": true, + "sku": true, + "sort_order": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_TYPE_VALUE_OPTION_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_OPT_TYPE_VAL_OPT_ID_CAT_PRD_OPT_OPT_ID": true + } + }, + "catalog_product_option_type_price": { + "column": { + "option_type_price_id": true, + "option_type_id": true, + "store_id": true, + "price": true, + "price_type": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "FK_B523E3378E8602F376CC415825576B7F": true, + "CATALOG_PRODUCT_OPTION_TYPE_PRICE_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_OPTION_TYPE_PRICE_OPTION_TYPE_ID_STORE_ID": true + } + }, + "catalog_product_option_type_title": { + "column": { + "option_type_title_id": true, + "option_type_id": true, + "store_id": true, + "title": true + }, + "index": { + "CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "FK_C085B9CF2C2A302E8043FDEA1937D6A2": true, + "CATALOG_PRODUCT_OPTION_TYPE_TITLE_STORE_ID_STORE_STORE_ID": true, + "CATALOG_PRODUCT_OPTION_TYPE_TITLE_OPTION_TYPE_ID_STORE_ID": true + } + }, + "catalog_eav_attribute": { + "column": { + "attribute_id": true, + "frontend_input_renderer": true, + "is_global": true, + "is_visible": true, + "is_searchable": true, + "is_filterable": true, + "is_comparable": true, + "is_visible_on_front": true, + "is_html_allowed_on_front": true, + "is_used_for_price_rules": true, + "is_filterable_in_search": true, + "used_in_product_listing": true, + "used_for_sort_by": true, + "apply_to": true, + "is_visible_in_advanced_search": true, + "position": true, + "is_wysiwyg_enabled": true, + "is_used_for_promo_rules": true, + "is_required_in_admin_store": true, + "is_used_in_grid": true, + "is_visible_in_grid": true, + "is_filterable_in_grid": true + }, + "index": { + "CATALOG_EAV_ATTRIBUTE_USED_FOR_SORT_BY": true, + "CATALOG_EAV_ATTRIBUTE_USED_IN_PRODUCT_LISTING": true + }, + "constraint": { + "PRIMARY": true, + "CATALOG_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true + } + }, + "catalog_product_relation": { + "column": { + "parent_id": true, + "child_id": true + }, + "index": { + "CATALOG_PRODUCT_RELATION_CHILD_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_RELATION_CHILD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_RELATION_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true + } + }, + "catalog_product_index_eav": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_eav_decimal": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_DEC_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_price": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price": true, + "final_price": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_MIN_PRICE": true, + "CAT_PRD_IDX_PRICE_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "CAT_PRD_IDX_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "catalog_product_index_tier_price": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_TIER_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_TIER_PRICE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_TIER_PRICE_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "CAT_PRD_IDX_TIER_PRICE_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_IDX_TIER_PRICE_WS_ID_STORE_WS_WS_ID": true + } + }, + "catalog_product_index_website": { + "column": { + "website_id": true, + "website_date": true, + "rate": true, + "default_store_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_WEBSITE_WEBSITE_DATE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_WS_WS_ID_STORE_WS_WS_ID": true + } + }, + "catalog_product_index_price_cfg_opt_agr_idx": { + "column": { + "parent_id": true, + "child_id": true, + "customer_group_id": true, + "website_id": true, + "price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_cfg_opt_agr_tmp": { + "column": { + "parent_id": true, + "child_id": true, + "customer_group_id": true, + "website_id": true, + "price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_cfg_opt_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_cfg_opt_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_final_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "orig_price": true, + "price": true, + "min_price": true, + "max_price": true, + "tier_price": true, + "base_tier": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_final_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "orig_price": true, + "price": true, + "min_price": true, + "max_price": true, + "tier_price": true, + "base_tier": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_opt_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_opt_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_opt_agr_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_opt_agr_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "option_id": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_eav_idx": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_IDX_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_IDX_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_IDX_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_IDX_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_eav_tmp": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_TMP_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_TMP_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_TMP_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_TMP_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_eav_decimal_idx": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_IDX_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_DEC_IDX_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_eav_decimal_tmp": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_TMP_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_IDX_EAV_DEC_TMP_ENTT_ID_ATTR_ID_STORE_ID_VAL_SOURCE_ID": true + } + }, + "catalog_product_index_price_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price": true, + "final_price": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_PRICE_IDX_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_IDX_WEBSITE_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_IDX_MIN_PRICE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price": true, + "final_price": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_PRICE_TMP_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_TMP_WEBSITE_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_TMP_MIN_PRICE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_category_product_index_tmp": { + "column": { + "category_id": true, + "product_id": true, + "position": true, + "is_parent": true, + "store_id": true, + "visibility": true + }, + "constraint": { + "PRIMARY": true + }, + "index": { + "CAT_CTGR_PRD_IDX_TMP_PRD_ID_CTGR_ID_STORE_ID": true + } + }, + "catalog_product_entity_media_gallery_value_to_entity": { + "column": { + "value_id": true, + "entity_id": true + }, + "constraint": { + "FK_A6C6C8FAA386736921D3A7C4B50B1185": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_VAL_ID_ENTT_ID": true + } + }, + "catalog_product_index_eav_replica": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_REPLICA_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_REPLICA_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_REPLICA_VALUE": true, + "CATALOG_PRODUCT_INDEX_EAV_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_VALUE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_eav_decimal_replica": { + "column": { + "entity_id": true, + "attribute_id": true, + "store_id": true, + "value": true, + "source_id": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_REPLICA_VALUE": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_ATTRIBUTE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_STORE_ID": true, + "CATALOG_PRODUCT_INDEX_EAV_DECIMAL_VALUE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_replica": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "tax_class_id": true, + "price": true, + "final_price": true, + "min_price": true, + "max_price": true, + "tier_price": true + }, + "index": { + "CATALOG_PRODUCT_INDEX_PRICE_REPLICA_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_REPLICA_MIN_PRICE": true, + "CAT_PRD_IDX_PRICE_REPLICA_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true, + "CATALOG_PRODUCT_INDEX_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOG_PRODUCT_INDEX_PRICE_MIN_PRICE": true, + "CAT_PRD_IDX_PRICE_WS_ID_CSTR_GROUP_ID_MIN_PRICE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_category_product_index_replica": { + "column": { + "category_id": true, + "product_id": true, + "position": true, + "is_parent": true, + "store_id": true, + "visibility": true + }, + "index": { + "CAT_CTGR_PRD_IDX_REPLICA_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, + "IDX_87EB2E3059853CF89A75B4C55074810B": true, + "CAT_CTGR_PRD_IDX_PRD_ID_STORE_ID_CTGR_ID_VISIBILITY": true, + "CAT_CTGR_PRD_IDX_STORE_ID_CTGR_ID_VISIBILITY_IS_PARENT_POSITION": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_frontend_action": { + "column": { + "action_id": true, + "type_id": true, + "visitor_id": true, + "customer_id": true, + "product_id": true, + "added_at": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_FRONTEND_ACTION_CSTR_ID_CSTR_ENTT_ENTT_ID": true, + "CAT_PRD_FRONTEND_ACTION_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_FRONTEND_ACTION_VISITOR_ID_PRODUCT_ID_TYPE_ID": true, + "CATALOG_PRODUCT_FRONTEND_ACTION_CUSTOMER_ID_PRODUCT_ID_TYPE_ID": true + } } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 9f1fb020ef95a..86dcbee196a87 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -70,7 +70,8 @@ <preference for="Magento\Catalog\Api\Data\ProductRender\FormattedPriceInfoInterface" type="Magento\Catalog\Model\ProductRender\FormattedPriceInfo" /> <preference for="Magento\Framework\Indexer\BatchProviderInterface" type="Magento\Framework\Indexer\BatchProvider" /> <preference for="Magento\Catalog\Model\Indexer\Product\Price\UpdateIndexInterface" type="Magento\Catalog\Model\Indexer\Product\Price\InvalidateIndex" /> - <preference for="\Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface" type="\Magento\Catalog\Model\Product\Gallery\ImagesConfigFactory" /> + <preference for="Magento\Catalog\Model\Product\Gallery\ImagesConfigFactoryInterface" type="Magento\Catalog\Model\Product\Gallery\ImagesConfigFactory" /> + <preference for="Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface" type="Magento\Catalog\Model\Product\Configuration\Item\ItemResolverComposite" /> <type name="Magento\Customer\Model\ResourceModel\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type> @@ -173,10 +174,17 @@ </type> <type name="Magento\Catalog\Helper\Data"> <arguments> - <argument name="templateFilterModel" xsi:type="string">Magento\Catalog\Model\Template\Filter</argument> + <argument name="templateFilterModel" xsi:type="string">Magento\Widget\Model\Template\Filter</argument> <argument name="catalogSession" xsi:type="object">Magento\Catalog\Model\Session\Proxy</argument> </arguments> </type> + <type name="Magento\Catalog\Helper\Output"> + <arguments> + <argument name="directivePatterns" xsi:type="array"> + <item name="construct" xsi:type="const">\Magento\Framework\Filter\Template::CONSTRUCTION_PATTERN</item> + </argument> + </arguments> + </type> <type name="Magento\Catalog\Model\Config\Source\GridPerPage"> <arguments> <argument name="perPageValues" xsi:type="string">9,15,30</argument> @@ -231,7 +239,8 @@ </arguments> </type> <type name="Magento\Store\Model\ResourceModel\Website"> - <plugin name="priceIndexerOnWebsiteDelete" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\Website"/> + <plugin name="invalidatePriceIndexerOnWebsite" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\Website"/> + <plugin name="categoryProductWebsiteAfterDelete" type="\Magento\Catalog\Model\Indexer\Category\Product\Plugin\Website"/> </type> <type name="Magento\Store\Model\ResourceModel\Store"> <plugin name="storeViewResourceAroundSave" type="Magento\Catalog\Model\Indexer\Category\Flat\Plugin\StoreView"/> @@ -915,6 +924,7 @@ <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\LinkedProductSelectBuilderByIndexPrice"> <arguments> <argument name="baseSelectProcessor" xsi:type="object">Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor</argument> + <argument name="priceTableResolver" xsi:type="object">Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver</argument> </arguments> </type> <type name="Magento\Catalog\Model\Product\Price\CostStorage"> @@ -1080,4 +1090,69 @@ <argument name="nativeAttributeConditionBuilder" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\ConditionProcessor\ConditionBuilder\NativeAttributeCondition</argument> </arguments> </type> + <type name="Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory"> + <arguments> + <argument name="dimensionProviders" xsi:type="array"> + <!-- @see \Magento\Store\Model\Indexer\WebsiteDimensionProvider::DIMENSION_NAME --> + <item name="ws" xsi:type="object">Magento\Store\Model\Indexer\WebsiteDimensionProvider</item> + <!-- @see \Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider::DIMENSION_NAME --> + <item name="cg" xsi:type="object">Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider</item> + </argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"> + <arguments> + <argument name="priceTableResolver" xsi:type="object">Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver\Proxy</argument> + <argument name="storeManager" xsi:type="object">Magento\Store\Model\StoreManagerInterface\Proxy</argument> + <argument name="context" xsi:type="object">Magento\Framework\App\Http\Context\Proxy</argument> + <!-- Unccomment after fix issue with Proxy generation --> + <!--<argument name="dimensionModeConfiguration" xsi:type="object">--> + <!--Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration\Proxy--> + <!--</argument>--> + </arguments> + </type> + <type name="Magento\Catalog\Model\ResourceModel\Layer\Filter\Price"> + <arguments> + <argument name="priceTableResolver" xsi:type="object"> + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + </argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\CustomOptionPriceModifier"> + <arguments> + <argument name="tableStrategy" xsi:type="object">Magento\Catalog\Model\ResourceModel\Product\Indexer\TemporaryTableStrategy</argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer"> + <arguments> + <argument name="connectionName" xsi:type="string">indexer</argument> + <argument name="tableResolver" xsi:type="object">Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver</argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier"> + <arguments> + <argument name="priceModifiers" xsi:type="array"> + <item name="customOptionPriceModifier" xsi:type="object">Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\CustomOptionPriceModifier</item> + </argument> + </arguments> + </type> + <virtualType name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\VirtualProductPrice" type="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\SimpleProductPrice"> + <arguments> + <argument name="productType" xsi:type="string">virtual</argument> + </arguments> + </virtualType> + <type name="Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand"> + <arguments> + <argument name="dimensionSwitchers" xsi:type="array"> + <item name="catalog_product_price" xsi:type="object">Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher</item> + </argument> + </arguments> + </type> + <type name="Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand"> + <arguments> + <argument name="indexers" xsi:type="array"> + <item name="catalog_product_price" xsi:type="string">catalog_product_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 659ba2b731366..55098037191e8 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -79,6 +79,18 @@ <argument name="typeId" xsi:type="string">recently_compared_product</argument> </arguments> </virtualType> + <type name="Magento\Framework\View\Element\Message\MessageConfigurationsPool"> + <arguments> + <argument name="configurationsMap" xsi:type="array"> + <item name="addCompareSuccessMessage" xsi:type="array"> + <item name="renderer" xsi:type="const">\Magento\Framework\View\Element\Message\Renderer\BlockRenderer::CODE</item> + <item name="data" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Catalog::messages/addCompareSuccessMessage.phtml</item> + </item> + </item> + </argument> + </arguments> + </type> <type name="Magento\Catalog\Block\Product\View\Gallery"> <arguments> <argument name="galleryImagesConfig" xsi:type="array"> @@ -102,5 +114,6 @@ </type> <type name="Magento\Framework\App\ResourceConnection"> <plugin name="get_catalog_category_product_index_table_name" type="Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver"/> + <plugin name="get_catalog_product_price_index_table_name" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"/> </type> </config> diff --git a/app/code/Magento/Catalog/etc/product_types.xml b/app/code/Magento/Catalog/etc/product_types.xml index fe4922ab8fa1f..fdcb67ae484d2 100644 --- a/app/code/Magento/Catalog/etc/product_types.xml +++ b/app/code/Magento/Catalog/etc/product_types.xml @@ -7,11 +7,13 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd"> <type name="simple" label="Simple Product" modelInstance="Magento\Catalog\Model\Product\Type\Simple" indexPriority="10" sortOrder="10"> + <indexerModel instance="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\SimpleProductPrice" /> <customAttributes> <attribute name="refundable" value="true"/> </customAttributes> </type> <type name="virtual" label="Virtual Product" modelInstance="Magento\Catalog\Model\Product\Type\Virtual" indexPriority="20" sortOrder="40"> + <indexerModel instance="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\VirtualProductPrice" /> <customAttributes> <attribute name="is_real_product" value="false"/> <attribute name="refundable" value="false"/> diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml index a0d3e850b3c64..2a5d60222e9f8 100644 --- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml @@ -17,5 +17,6 @@ </type> <type name="Magento\Framework\App\ResourceConnection"> <plugin name="get_catalog_category_product_index_table_name" type="Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver"/> + <plugin name="get_catalog_product_price_index_table_name" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"/> </type> </config> diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml index a0d3e850b3c64..2a5d60222e9f8 100644 --- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml @@ -17,5 +17,6 @@ </type> <type name="Magento\Framework\App\ResourceConnection"> <plugin name="get_catalog_category_product_index_table_name" type="Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver"/> + <plugin name="get_catalog_product_price_index_table_name" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"/> </type> </config> diff --git a/app/code/Magento/Catalog/etc/widget.xml b/app/code/Magento/Catalog/etc/widget.xml index a11d206e2ce42..f3b37d7d32e31 100644 --- a/app/code/Magento/Catalog/etc/widget.xml +++ b/app/code/Magento/Catalog/etc/widget.xml @@ -296,7 +296,7 @@ </parameters> <containers> <container name="sidebar.main"> - <template name="default" value="list" /> + <template name="default" value="sidebar" /> </container> <container name="content"> <template name="grid" value="grid" /> diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index f2a7cf0b1950b..f2a3ab8f83f24 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -13,8 +13,8 @@ Position,Position Day,Day Month,Month Year,Year -"<label class=""label""><span>from</span></label>","<label class=""label""><span>from</span></label>" -"<label class=""label""><span>to</span></label>","<label class=""label""><span>to</span></label>" +from,from +to,to [GLOBAL],[GLOBAL] [WEBSITE],[WEBSITE] "[STORE VIEW]","[STORE VIEW]" @@ -658,7 +658,6 @@ Comma-separated.,Comma-separated. "Product Listing Sort by","Product Listing Sort by" "Allow All Products per Page","Allow All Products per Page" "Whether to show ""All"" option in the ""Show X Per Page"" dropdown","Whether to show ""All"" option in the ""Show X Per Page"" dropdown" -"Allow Dynamic Media URLs","Allow Dynamic Media URLs" "E.g. {{media url=""path/to/image.jpg""}} {{skin url=""path/to/picture.gif""}}. Dynamic directives parsing impacts catalog performance.","E.g. {{media url=""path/to/image.jpg""}} {{skin url=""path/to/picture.gif""}}. Dynamic directives parsing impacts catalog performance." "Product Image Placeholders","Product Image Placeholders" "Search Engine Optimization","Search Engine Optimization" diff --git a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js index 9053c700d1a3b..0677b0a5811c2 100644 --- a/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/Catalog/view/adminhtml/requirejs-config.js @@ -17,5 +17,18 @@ var config = { }, deps: [ 'Magento_Catalog/catalog/product' - ] + ], + config: { + mixins: { + 'Magento_Catalog/js/components/use-parent-settings/select': { + 'Magento_Catalog/js/components/use-parent-settings/toggle-disabled-mixin': true + }, + 'Magento_Catalog/js/components/use-parent-settings/textarea': { + 'Magento_Catalog/js/components/use-parent-settings/toggle-disabled-mixin': true + }, + 'Magento_Catalog/js/components/use-parent-settings/single-checkbox': { + 'Magento_Catalog/js/components/use-parent-settings/toggle-disabled-mixin': true + } + } + } }; diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml index 2976f1be14fa5..dafea71f872d0 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml @@ -42,7 +42,7 @@ </settings> </dataProvider> </dataSource> - <fieldset name="general"> + <fieldset name="general" sortOrder="5"> <settings> <collapsible>false</collapsible> <label/> @@ -452,34 +452,34 @@ </checkbox> </formElements> </field> - <field name="custom_design" sortOrder="180" formElement="select"> + <field name="custom_design" component="Magento_Catalog/js/components/use-parent-settings/select" sortOrder="180" formElement="select"> <settings> <dataType>string</dataType> <label translate="true">Theme</label> <imports> - <link name="disabled">${ $.parentName }.custom_use_parent_settings:checked</link> + <link name="serviceDisabled">${ $.parentName }.custom_use_parent_settings:checked</link> </imports> </settings> </field> - <field name="page_layout" sortOrder="190" formElement="select" class="Magento\Catalog\Ui\Component\Form\Field\Category\PageLayout"> + <field name="page_layout" sortOrder="190" formElement="select" component="Magento_Catalog/js/components/use-parent-settings/select" class="Magento\Catalog\Ui\Component\Form\Field\Category\PageLayout"> <settings> <dataType>string</dataType> <label translate="true">Layout</label> <imports> - <link name="disabled">${ $.parentName }.custom_use_parent_settings:checked</link> + <link name="serviceDisabled">${ $.parentName }.custom_use_parent_settings:checked</link> </imports> </settings> </field> - <field name="custom_layout_update" sortOrder="200" formElement="textarea"> + <field name="custom_layout_update" component="Magento_Catalog/js/components/use-parent-settings/textarea" sortOrder="200" formElement="textarea"> <settings> <dataType>string</dataType> <label translate="true">Layout Update XML</label> <imports> - <link name="disabled">ns = ${ $.ns }, index = custom_use_parent_settings :checked</link> + <link name="serviceDisabled">${ $.parentName }.custom_use_parent_settings:checked</link> </imports> </settings> </field> - <field name="custom_apply_to_products" component="Magento_Ui/js/form/element/single-checkbox" sortOrder="210" formElement="checkbox"> + <field name="custom_apply_to_products" component="Magento_Catalog/js/components/use-parent-settings/single-checkbox" sortOrder="210" formElement="checkbox"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="default" xsi:type="number">0</item> @@ -492,7 +492,7 @@ <dataType>boolean</dataType> <label translate="true">Apply Design to Products</label> <imports> - <link name="disabled">ns = ${ $.ns }, index = custom_use_parent_settings:checked</link> + <link name="serviceDisabled">${ $.parentName }.custom_use_parent_settings:checked</link> </imports> </settings> <formElements> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js index bc44128663cd0..2359d1499f834 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js @@ -33,7 +33,6 @@ define([ data = {}, parameters = {}, root = {}, - len = 0, key = ''; /** @@ -160,15 +159,15 @@ define([ * @returns {void} */ categoryLoader.buildCategoryTree = function (parent, nodeConfig) { - var j = 0; + var i = 0; if (!nodeConfig) { return null; } if (parent && nodeConfig && nodeConfig.length) { - for (j = 0; j < nodeConfig.length; j++) { - categoryLoader.processCategoryTree(parent, nodeConfig, j); + for (i; i < nodeConfig.length; i++) { + categoryLoader.processCategoryTree(parent, nodeConfig, i); } } }; @@ -180,14 +179,15 @@ define([ * @returns {Object} */ categoryLoader.buildHashChildren = function (hash, node) { - var j = 0; + var i = 0, + len; if (node.childNodes.length > 0 || node.loaded === false && node.loading === false) { hash.children = []; - for (j = 0, len = node.childNodes.length; j < len; j++) { + for (i, len = node.childNodes.length; i < len; i++) { hash.children = hash.children ? hash.children : []; - hash.children.push(this.buildHash(node.childNodes[j])); + hash.children.push(this.buildHash(node.childNodes[i])); } } diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/select.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/select.js new file mode 100644 index 0000000000000..1ddb24f3eefbb --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/select.js @@ -0,0 +1,15 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + 'Magento_Ui/js/form/element/select' +], function (Component) { + 'use strict'; + + return Component; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/single-checkbox.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/single-checkbox.js new file mode 100644 index 0000000000000..0f166d3b45582 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/single-checkbox.js @@ -0,0 +1,15 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + 'Magento_Ui/js/form/element/single-checkbox' +], function (Component) { + 'use strict'; + + return Component; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/textarea.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/textarea.js new file mode 100644 index 0000000000000..3ef2bb21241a7 --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/textarea.js @@ -0,0 +1,15 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @api + */ +define([ + 'Magento_Ui/js/form/element/textarea' +], function (Component) { + 'use strict'; + + return Component; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/toggle-disabled-mixin.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/toggle-disabled-mixin.js new file mode 100644 index 0000000000000..d140cc0fad74e --- /dev/null +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/use-parent-settings/toggle-disabled-mixin.js @@ -0,0 +1,62 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore' +], function (_) { + 'use strict'; + + var mixin = { + defaults: { + imports: { + toggleDisabled: '${ $.parentName }.custom_use_parent_settings:checked' + }, + useParent: false, + useDefaults: false + }, + + /** + * Disable form input if settings for parent section is used + * or default value is applied. + * + * @param {Boolean} isUseParent + */ + toggleDisabled: function (isUseParent) { + var disabled = this.useParent = isUseParent; + + if (!disabled && !_.isUndefined(this.service)) { + disabled = !!this.isUseDefault(); + } + + this.saveUseDefaults(); + this.disabled(disabled); + }, + + /** + * Stores original state of the field. + */ + saveUseDefaults: function () { + this.useDefaults = this.disabled(); + }, + + /** @inheritdoc */ + setInitialValue: function () { + this._super(); + this.isUseDefault(this.useDefaults); + + return this; + }, + + /** @inheritdoc */ + toggleUseDefault: function (state) { + this._super(); + this.disabled(state || this.useParent); + } + }; + + return function (target) { + return target.extend(mixin); + }; +}); diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js b/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js index 475c9d2dc0601..94300e31f74b5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/product/weight-handler.js @@ -67,10 +67,10 @@ define([ }, /** - * Has weight swither + * Has weight switcher * @returns {*} */ - hasWeightSwither: function () { + hasWeightSwitcher: function () { return this.$weightSwitcher().is(':visible'); }, @@ -107,7 +107,7 @@ define([ 'Magento_Catalog/js/product/weight-handler': function () { this.bindAll(); - if (this.hasWeightSwither()) { + if (this.hasWeightSwitcher()) { this.switchWeight(); } }, diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/image-preview.html b/app/code/Magento/Catalog/view/adminhtml/web/template/image-preview.html index 04b4990f9cace..bf17624517f2a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/image-preview.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/image-preview.html @@ -14,7 +14,8 @@ event="load: $parent.onPreviewLoad.bind($parent)" attr=" src: $parent.getFilePreview($file), - alt: $file.name"> + alt: $file.name, + title: $file.name"> </a> <div class="actions"> diff --git a/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml b/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml index 86168c742c0f1..ce1561e382eed 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/price/amount/default.phtml @@ -19,9 +19,8 @@ <?= ($block->getPriceDisplayLabel()) ? 'data-label="' . $block->getPriceDisplayLabel() . $block->getPriceDisplayInclExclTaxes() . '"' : '' ?> data-price-amount="<?= /* @escapeNotVerified */ $block->getDisplayValue() ?>" data-price-type="<?= /* @escapeNotVerified */ $block->getPriceType() ?>" - class="price-wrapper <?= /* @escapeNotVerified */ $block->getPriceWrapperCss() ?>"> - <?= /* @escapeNotVerified */ $block->formatCurrency($block->getDisplayValue(), (bool)$block->getIncludeContainer()) ?> - </span> + class="price-wrapper <?= /* @escapeNotVerified */ $block->getPriceWrapperCss() ?>" + ><?= /* @escapeNotVerified */ $block->formatCurrency($block->getDisplayValue(), (bool)$block->getIncludeContainer()) ?></span> <?php if ($block->hasAdjustmentsHtml()): ?> <?= $block->getAdjustmentsHtml() ?> <?php endif; ?> diff --git a/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image.html b/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image.html index 318a6ceed69d1..cf76762b1ff58 100644 --- a/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image.html +++ b/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image.html @@ -11,6 +11,7 @@ class="product-image-photo" attr="src: getImageUrl($row()), alt: getLabel($row()), + title: getLabel($row()), width: getResizedImageWidth($row()), height: getResizedImageHeight($row())"/> </a> diff --git a/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image_with_borders.html b/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image_with_borders.html index 2baa9926df5f1..68b7f4e386896 100644 --- a/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image_with_borders.html +++ b/app/code/Magento/Catalog/view/base/web/template/product/list/columns/image_with_borders.html @@ -14,7 +14,7 @@ data-bind="style: {'padding-bottom': getHeight($row())/getWidth($row()) * 100 + '%'}"> <img class="product-image-photo" data-bind="attr: {src: getImageUrl($row()), - alt: getLabel($row())}" /> + alt: getLabel($row()), title: getLabel($row())}" /> </span> </span> </a> diff --git a/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml b/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml new file mode 100644 index 0000000000000..5f44c42e17c57 --- /dev/null +++ b/app/code/Magento/Catalog/view/frontend/templates/messages/addCompareSuccessMessage.phtml @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +// @codingStandardsIgnoreFile +/** @var \Magento\Framework\View\Element\Template $block */ +?> +<?= $block->escapeHtml(__( + 'You added product %1 to the <a href="%2">comparison list</a>.', + $block->getData('product_name'), + $block->getData('compare_list_url')), + ['a'] +); diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml index c54ce5340851c..c4aa84704b598 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml @@ -7,4 +7,9 @@ /** @var \Magento\Catalog\ViewModel\Product\Breadcrumbs $viewModel */ $viewModel = $block->getData('viewModel'); ?> -<div class="breadcrumbs" data-mage-init='<?= /* @escapeNotVerified */ $viewModel->getJsonConfiguration() ?>'></div> +<div class="breadcrumbs"></div> +<script type="text/x-magento-init"> + { + ".breadcrumbs": <?= $viewModel->getJsonConfigurationHtmlEscaped() ?> + } +</script> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml index f7799b30436be..e970ade6cee96 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml @@ -46,7 +46,7 @@ $_helper = $this->helper('Magento\Catalog\Helper\Output'); <?php /** @var $_product \Magento\Catalog\Model\Product */ ?> <?php foreach ($_productCollection as $_product): ?> <li class="item product product-item"> - <div class="product-item-info" data-container="product-grid"> + <div class="product-item-info" data-container="product-<?= /* @escapeNotVerified */ $viewMode ?>"> <?php $productImage = $block->getImage($_product, $imageDisplayArea); if ($pos != null) { diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml index 16a5147e13458..adf0f44d0c831 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addto/compare.phtml @@ -6,7 +6,7 @@ // @codingStandardsIgnoreFile -/** @var $block \Magento\Catalog\Block\Catalog\Product\View\Addto\Compare */ +/** @var $block \Magento\Catalog\Block\Product\View\Addto\Compare */ ?> <a href="#" data-post='<?= /* @escapeNotVerified */ $block->getPostDataParams() ?>' diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml index f1b2f1a214945..2d2c91aadd473 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/compared/sidebar.phtml @@ -17,10 +17,8 @@ 'listing' => [ 'displayMode' => 'grid' ], - 'column' => [ - 'image' => [ - 'imageCode' => 'recently_compared_products_images_names_widget' - ] + 'image' => [ + 'imageCode' => 'recently_compared_products_images_names_widget' ] ] ); diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index 8fcac2f9f1d65..3fb641733eb22 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -49,6 +49,23 @@ define([ }); }, + /** + * @private + */ + _redirect: function (url) { + var urlParts, locationParts, forceReload; + + urlParts = url.split('#'); + locationParts = window.location.href.split('#'); + forceReload = urlParts[0] === locationParts[0]; + + window.location.assign(url); + + if (forceReload) { + window.location.reload(); + } + }, + /** * @return {Boolean} */ @@ -62,34 +79,28 @@ define([ * @param {Object} form */ submitForm: function (form) { - var addToCartButton, self = this; - - if (form.has('input[type="file"]').length && form.find('input[type="file"]').val() !== '') { - self.element.off('submit'); - // disable 'Add to Cart' button - addToCartButton = $(form).find(this.options.addToCartButtonSelector); - addToCartButton.prop('disabled', true); - addToCartButton.addClass(this.options.addToCartButtonDisabledClass); - form.submit(); - } else { - self.ajaxSubmit(form); - } + this.ajaxSubmit(form); }, /** * @param {String} form */ ajaxSubmit: function (form) { - var self = this; + var self = this, + formData; $(self.options.minicartSelector).trigger('contentLoading'); self.disableAddToCartButton(form); + formData = new FormData(form[0]); $.ajax({ url: form.attr('action'), - data: form.serialize(), + data: formData, type: 'post', dataType: 'json', + cache: false, + contentType: false, + processData: false, /** @inheritdoc */ beforeSend: function () { @@ -125,7 +136,8 @@ define([ parameters.push(eventData.redirectParameters.join('&')); res.backUrl = parameters.join('#'); } - window.location = res.backUrl; + + self._redirect(res.backUrl); return; } @@ -147,6 +159,13 @@ define([ .html(res.product.statusText); } self.enableAddToCartButton(form); + }, + + /** @inheritdoc */ + complete: function (res) { + if (res.state() === 'rejected') { + location.reload(); + } } }); }, diff --git a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js index 014002bdc4af9..e571baa23e497 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/product/storage/storage-service.js @@ -47,7 +47,7 @@ define([ * @param {*} data */ add: function (data) { - if (!utils.compare(data, this.data()).equal) { + if (!_.isEmpty(data) && !utils.compare(data, this.data()).equal) { this.data(_.extend(utils.copy(this.data()), data)); } }, diff --git a/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json b/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json deleted file mode 100644 index c03b128d9ad83..0000000000000 --- a/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php index a1f581743a645..ebd8671de02fb 100644 --- a/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php +++ b/app/code/Magento/CatalogGraphQl/Model/AttributesJoiner.php @@ -28,6 +28,10 @@ public function join(FieldNode $fieldNode, AbstractCollection $collection) : voi /** @var FieldNode $field */ foreach ($query as $field) { + if ($field->kind === 'InlineFragment') { + continue; + } + if (!$collection->isAttributeAdded($field->name->value)) { $collection->addAttributeToSelect($field->name->value); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php index baa456c7821ed..dbe58a9c77cd0 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php @@ -26,6 +26,10 @@ public function calculate(FieldNode $fieldNode) : int $depth = count($selections) ? 1 : 0; $childrenDepth = [0]; foreach ($selections as $node) { + if ($node->kind === 'InlineFragment') { + continue; + } + $childrenDepth[] = $this->calculate($node); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php index eb57873850b80..0401e1c42331e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php @@ -37,7 +37,7 @@ public function calculate(int $rootCategoryId) : int { $connection = $this->resourceConnection->getConnection(); $select = $connection->select() - ->from($connection->getTableName('catalog_category_entity'), 'level') + ->from($this->resourceConnection->getTableName('catalog_category_entity'), 'level') ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); return (int) $connection->fetchOne($select); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Breadcrumbs.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Breadcrumbs.php new file mode 100644 index 0000000000000..d49c7f5e5674d --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Breadcrumbs.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category; + +use \Magento\CatalogGraphQl\Model\Resolver\Category\DataProvider\Breadcrumbs as BreadcrumbsDataProvider; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; + +/** + * Retrieves breadcrumbs + */ +class Breadcrumbs implements ResolverInterface +{ + /** + * @var BreadcrumbsDataProvider + */ + private $breadcrumbsDataProvider; + + /** + * @var ValueFactory + */ + private $valueFactory; + + /** + * @param BreadcrumbsDataProvider $breadcrumbsDataProvider + * @param ValueFactory $valueFactory + */ + public function __construct( + BreadcrumbsDataProvider $breadcrumbsDataProvider, + ValueFactory $valueFactory + ) { + $this->breadcrumbsDataProvider = $breadcrumbsDataProvider; + $this->valueFactory = $valueFactory; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null): Value + { + if (!isset($value['path'])) { + $result = function () { + return null; + }; + return $this->valueFactory->create($result); + } + + $result = function () use ($value) { + $breadcrumbsData = $this->breadcrumbsDataProvider->getData($value['path']); + return count($breadcrumbsData) ? $breadcrumbsData : null; + }; + return $this->valueFactory->create($result); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php new file mode 100644 index 0000000000000..9e23c4f1e9736 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/DataProvider/Breadcrumbs.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Category\DataProvider; + +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; + +/** + * Breadcrumbs data provider + */ +class Breadcrumbs +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @param CollectionFactory $collectionFactory + */ + public function __construct( + CollectionFactory $collectionFactory + ) { + $this->collectionFactory = $collectionFactory; + } + + /** + * @param string $categoryPath + * @return array + */ + public function getData(string $categoryPath): array + { + $breadcrumbsData = []; + + $pathCategoryIds = explode('/', $categoryPath); + $parentCategoryIds = array_slice($pathCategoryIds, 2, -1); + + if (count($parentCategoryIds)) { + $collection = $this->collectionFactory->create(); + $collection->addAttributeToSelect(['name', 'url_key']); + $collection->addAttributeToFilter('entity_id', $parentCategoryIds); + + foreach ($collection as $category) { + $breadcrumbsData[] = [ + 'category_id' => $category->getId(), + 'category_name' => $category->getName(), + 'category_level' => $category->getLevel(), + 'category_url_key' => $category->getUrlKey(), + ]; + } + } + return $breadcrumbsData; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Attributes/Collection.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Attributes/Collection.php index d0413813aab37..ab0531ad09513 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Attributes/Collection.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Attributes/Collection.php @@ -46,6 +46,24 @@ public function getAttributes() : AttributeCollection $this->collection = $this->collectionFactory->create(); $this->collection->addFieldToFilter('is_user_defined', '1'); $this->collection->addFieldToFilter('attribute_code', ['neq' => 'cost']); + $this->collection->addFieldToFilter( + [ + 'is_comparable', + 'is_filterable', + 'is_filterable_in_search', + 'is_visible_on_front', + 'used_in_product_listing', + 'used_for_sort_by' + ], + [ + ['eq' => '1'], + ['eq' => '1'], + ['eq' => '1'], + ['eq' => '1'], + ['eq' => '1'], + ['eq' => '1'] + ] + ); } return $this->collection->load(); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 3c01579410638..d2fc174fb1ed5 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -24,9 +24,9 @@ class CategoryTree { /** - * In depth we need to calculate only children nodes, so 2 first wrapped nodes should be ignored + * In depth we need to calculate only children nodes, so the first wrapped node should be ignored */ - const DEPTH_OFFSET = 2; + const DEPTH_OFFSET = 1; /** * @var CollectionFactory @@ -100,7 +100,7 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId) : array $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); $collection->setOrder('level'); $collection->getSelect()->orWhere( - $this->metadata->getMetadata(CategoryInterface::class)->getLinkField() . ' = ?', + $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() . ' = ?', $rootCategoryId ); return $this->processTree($collection->getIterator()); @@ -143,6 +143,10 @@ private function joinAttributesRecursively(Collection $collection, FieldNode $fi /** @var FieldNode $node */ foreach ($subSelection as $node) { + if ($node->kind === 'InlineFragment') { + continue; + } + $this->joinAttributesRecursively($collection, $node); } } diff --git a/app/code/Magento/CatalogGraphQl/Test/Mftf/composer.json b/app/code/Magento/CatalogGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 81a291e1527fc..0000000000000 --- a/app/code/Magento/CatalogGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-search": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav-graph-ql": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-graph-ql": "100.0.0-dev", - "magento/functional-test-module-store-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 762861de94e67..9235ec271a3c2 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -381,6 +381,14 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): CategoryProducts @doc(description: "The list of products assigned to the category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") + breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") +} + +type Breadcrumb @doc(description: "Breadcrumb item"){ + category_id: Int @doc(description: "Category ID") + category_name: String @doc(description: "Category name") + category_level: Int @doc(description: "Category level") + category_url_key: String @doc(description: "Category URL key") } type CustomizableRadioOption implements CustomizableOptionInterface @doc(description: "CustomizableRadioOption contains information about a set of radio buttons that are defined as part of a customizable option") { diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 097db293ab77c..6c3bef2a52b0c 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -294,7 +294,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ValidatorInterface::ERROR_MEDIA_PATH_NOT_ACCESSIBLE => 'Imported resource (image) does not exist in the local media storage', ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions', ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid', - ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually' + ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually', + ValidatorInterface::ERROR_DUPLICATE_MULTISELECT_VALUES => "Value for multiselect attribute %s contains duplicated values", ]; //@codingStandardsIgnoreEnd diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php index 5de9d3880b5d2..db5b07279ed1c 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php @@ -75,6 +75,7 @@ protected function initCategories() $collection->addAttributeToSelect('name') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); + $collection->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID); /* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */ foreach ($collection as $category) { $structure = explode(self::DELIMITER_CATEGORY, $category->getPath()); diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php index 17f7fae28ba75..f41596ad185a6 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php @@ -85,6 +85,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn const ERROR_DUPLICATE_URL_KEY = 'duplicatedUrlKey'; + const ERROR_DUPLICATE_MULTISELECT_VALUES = 'duplicatedMultiselectValues'; + /** * Value that means all entities (e.g. websites, groups etc.) */ diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php index dd33b94423696..afd018f077d20 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Type/AbstractType.php @@ -28,6 +28,13 @@ abstract class AbstractType */ public static $commonAttributesCache = []; + /** + * Maintain a list of invisible attributes + * + * @var array + */ + public static $invAttributesCache = []; + /** * Attribute Code to Id cache * @@ -278,7 +285,14 @@ protected function _initAttributes() } } foreach ($absentKeys as $attributeSetName => $attributeIds) { - $this->attachAttributesById($attributeSetName, $attributeIds); + $unknownAttributeIds = array_diff( + $attributeIds, + array_keys(self::$commonAttributesCache), + self::$invAttributesCache + ); + if ($unknownAttributeIds || $this->_forcedAttributesCodes) { + $this->attachAttributesById($attributeSetName, $attributeIds); + } } foreach ($entityAttributes as $attributeRow) { if (isset(self::$commonAttributesCache[$attributeRow['attribute_id']])) { @@ -303,37 +317,45 @@ protected function _initAttributes() protected function attachAttributesById($attributeSetName, $attributeIds) { foreach ($this->_prodAttrColFac->create()->addFieldToFilter( - 'main_table.attribute_id', - ['in' => $attributeIds] + ['main_table.attribute_id', 'main_table.attribute_code'], + [ + ['in' => $attributeIds], + ['in' => $this->_forcedAttributesCodes] + ] ) as $attribute) { $attributeCode = $attribute->getAttributeCode(); $attributeId = $attribute->getId(); if ($attribute->getIsVisible() || in_array($attributeCode, $this->_forcedAttributesCodes)) { - self::$commonAttributesCache[$attributeId] = [ - 'id' => $attributeId, - 'code' => $attributeCode, - 'is_global' => $attribute->getIsGlobal(), - 'is_required' => $attribute->getIsRequired(), - 'is_unique' => $attribute->getIsUnique(), - 'frontend_label' => $attribute->getFrontendLabel(), - 'is_static' => $attribute->isStatic(), - 'apply_to' => $attribute->getApplyTo(), - 'type' => \Magento\ImportExport\Model\Import::getAttributeType($attribute), - 'default_value' => strlen( - $attribute->getDefaultValue() - ) ? $attribute->getDefaultValue() : null, - 'options' => $this->_entityModel->getAttributeOptions( - $attribute, - $this->_indexValueAttributes - ), - ]; + if (!isset(self::$commonAttributesCache[$attributeId])) { + self::$commonAttributesCache[$attributeId] = [ + 'id' => $attributeId, + 'code' => $attributeCode, + 'is_global' => $attribute->getIsGlobal(), + 'is_required' => $attribute->getIsRequired(), + 'is_unique' => $attribute->getIsUnique(), + 'frontend_label' => $attribute->getFrontendLabel(), + 'is_static' => $attribute->isStatic(), + 'apply_to' => $attribute->getApplyTo(), + 'type' => \Magento\ImportExport\Model\Import::getAttributeType($attribute), + 'default_value' => strlen( + $attribute->getDefaultValue() + ) ? $attribute->getDefaultValue() : null, + 'options' => $this->_entityModel->getAttributeOptions( + $attribute, + $this->_indexValueAttributes + ), + ]; + } + self::$attributeCodeToId[$attributeCode] = $attributeId; $this->_addAttributeParams( $attributeSetName, self::$commonAttributesCache[$attributeId], $attribute ); + } else { + self::$invAttributesCache[] = $attributeId; } } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php index e1a6188551c0c..2aa2105991883 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php @@ -219,6 +219,12 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData) break; } } + + $uniqueValues = array_unique($values); + if (count($uniqueValues) != count($values)) { + $valid = false; + $this->_addMessages([RowValidatorInterface::ERROR_DUPLICATE_MULTISELECT_VALUES]); + } break; case 'datetime': $val = trim($rowData[$attrCode]); diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php index 0939acabbd5fd..e7ffe408cc732 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php @@ -170,14 +170,25 @@ public function move($fileName, $renameFileOff = false) } } + if ($this->getTmpDir()) { + $filePath = $this->getTmpDir() . '/'; + } else { + $filePath = ''; + } $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $fileName); + $filePath = $this->_directory->getRelativePath($filePath . $fileName); $this->_directory->writeFile( - $this->_directory->getRelativePath($this->getTmpDir() . '/' . $fileName), + $filePath, $read->readAll() ); } - $filePath = $this->_directory->getRelativePath($this->getTmpDir() . '/' . $fileName); + if ($this->getTmpDir()) { + $filePath = $this->getTmpDir() . '/'; + } else { + $filePath = ''; + } + $filePath = $this->_directory->getRelativePath($filePath . $fileName); $this->_setUploadFile($filePath); $destDir = $this->_directory->getAbsolutePath($this->getDestDir()); $result = $this->save($destDir); diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json b/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json deleted file mode 100644 index 1f223fc5f46b5..0000000000000 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php index 70ac3a4fa2e97..bd2fe896b8c0a 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/AbstractTypeTest.php @@ -134,8 +134,28 @@ protected function setUp() ->expects($this->any()) ->method('addFieldToFilter') ->with( - 'main_table.attribute_id', - ['in' => ['attribute_id', 'boolean_attribute']] + ['main_table.attribute_id', 'main_table.attribute_code'], + [ + [ + 'in' => + [ + 'attribute_id', + 'boolean_attribute', + ], + ], + [ + 'in' => + [ + 'related_tgtr_position_behavior', + 'related_tgtr_position_limit', + 'upsell_tgtr_position_behavior', + 'upsell_tgtr_position_limit', + 'thumbnail_label', + 'small_image_label', + 'image_label', + ], + ], + ] ) ->willReturn([$attribute1, $attribute2]); diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php index 1179783fdd3f9..64b925955519e 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php @@ -167,6 +167,26 @@ public function attributeValidationProvider() ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2'], true ], + [ + Import::BEHAVIOR_APPEND, + ['is_required' => true, 'type' => 'multiselect', + 'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']], + ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 1'], + false + ], + [ + Import::BEHAVIOR_APPEND, + ['is_required' => true, 'type' => 'multiselect', + 'options' => ['option 1' => 0, 'option 2' => 1, 'option 3']], + ['product_type' => 'any', 'attribute_code' => 'Option 3|Option 3|Option 3|Option 1'], + false + ], + [ + Import::BEHAVIOR_APPEND, + ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0]], + ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 1|Option 1|Option 1'], + false + ], [ Import::BEHAVIOR_APPEND, ['is_required' => true, 'type' => 'datetime'], diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php index ed95d5a0212c9..262593377aa2c 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/UploaderTest.php @@ -103,7 +103,7 @@ protected function setUp() public function testMoveFileUrl($fileUrl, $expectedHost, $expectedFileName) { $destDir = 'var/dest/dir'; - $expectedRelativeFilePath = $this->uploader->getTmpDir() . '/' . $expectedFileName; + $expectedRelativeFilePath = $expectedFileName; $this->directoryMock->expects($this->once())->method('isWritable')->with($destDir)->willReturn(true); $this->directoryMock->expects($this->any())->method('getRelativePath')->with($expectedRelativeFilePath); $this->directoryMock->expects($this->once())->method('getAbsolutePath')->with($destDir) @@ -139,7 +139,7 @@ public function testMoveFileName() { $destDir = 'var/dest/dir'; $fileName = 'test_uploader_file'; - $expectedRelativeFilePath = $this->uploader->getTmpDir() . '/' . $fileName; + $expectedRelativeFilePath = $fileName; $this->directoryMock->expects($this->once())->method('isWritable')->with($destDir)->willReturn(true); $this->directoryMock->expects($this->any())->method('getRelativePath')->with($expectedRelativeFilePath); $this->directoryMock->expects($this->once())->method('getAbsolutePath')->with($destDir) diff --git a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php index c9b6abeb72e23..2d011e24f4105 100644 --- a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php +++ b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php @@ -72,7 +72,7 @@ public function getProductStockStatusBySku($productSku, $scopeId = null); * @param float $qty * @param int $currentPage * @param int $pageSize - * @return \Magento\CatalogInventory\Api\Data\StockStatusCollectionInterface + * @return \Magento\CatalogInventory\Api\Data\StockItemCollectionInterface */ public function getLowStockItems($scopeId, $qty, $currentPage = 1, $pageSize = 0); diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php new file mode 100644 index 0000000000000..5e7210c1b7444 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Model\Indexer; + +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item; +use Magento\CatalogInventory\Model\Stock; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceModifierInterface; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\App\ObjectManager; + +/** + * Class for filter product price index. + */ +class ProductPriceIndexFilter implements PriceModifierInterface +{ + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @var Item + */ + private $stockItem; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var string + */ + private $connectionName; + + /** + * @param StockConfigurationInterface $stockConfiguration + * @param Item $stockItem + * @param ResourceConnection $resourceConnection + * @param string $connectionName + */ + public function __construct( + StockConfigurationInterface $stockConfiguration, + Item $stockItem, + ResourceConnection $resourceConnection = null, + $connectionName = 'indexer' + ) { + $this->stockConfiguration = $stockConfiguration; + $this->stockItem = $stockItem; + $this->resourceConnection = $resourceConnection ?: ObjectManager::getInstance()->get(ResourceConnection::class); + $this->connectionName = $connectionName; + } + + /** + * Remove out of stock products data from price index. + * + * @param IndexTableStructure $priceTable + * @param array $entityIds + * @return void + * + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void + { + if ($this->stockConfiguration->isShowOutOfStock()) { + return; + } + + $connection = $this->resourceConnection->getConnection($this->connectionName); + $select = $connection->select(); + $select->from( + ['price_index' => $priceTable->getTableName()], + [] + ); + $select->joinInner( + ['stock_item' => $this->stockItem->getMainTable()], + 'stock_item.product_id = price_index.' . $priceTable->getEntityField() + . ' AND stock_item.stock_id = ' . Stock::DEFAULT_STOCK_ID, + [] + ); + if ($this->stockConfiguration->getManageStock()) { + $stockStatus = $connection->getCheckSql( + 'use_config_manage_stock = 0 AND manage_stock = 0', + Stock::STOCK_IN_STOCK, + 'is_in_stock' + ); + } else { + $stockStatus = $connection->getCheckSql( + 'use_config_manage_stock = 0 AND manage_stock = 1', + 'is_in_stock', + Stock::STOCK_IN_STOCK + ); + } + $select->where($stockStatus . ' = ?', Stock::STOCK_OUT_OF_STOCK); + + $query = $select->deleteFromSelect('price_index'); + $connection->query($query); + } +} diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php b/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php new file mode 100644 index 0000000000000..c061c459bfb49 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/Plugin/PriceIndexUpdater.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Model\Plugin; + +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item; +use Magento\Catalog\Model\Indexer\Product\Price\Processor; +use Magento\Framework\Model\AbstractModel; + +/** + * Update product price index after product stock status changed. + */ +class PriceIndexUpdater +{ + /** + * @var Processor + */ + private $priceIndexProcessor; + + /** + * @param Processor $priceIndexProcessor + */ + public function __construct(Processor $priceIndexProcessor) + { + $this->priceIndexProcessor = $priceIndexProcessor; + } + + /** + * @param Item $subject + * @param Item $result + * @param AbstractModel $model + * @return Item + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSave(Item $subject, Item $result, AbstractModel $model): Item + { + $fields = [ + 'is_in_stock', + 'use_config_manage_stock', + 'manage_stock', + ]; + foreach ($fields as $field) { + if ($model->dataHasChangedFor($field)) { + $this->priceIndexProcessor->reindexRow($model->getProductId()); + break; + } + } + + return $result; + } + + /** + * @param Item $subject + * @param mixed $result + * @param int $websiteId + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterUpdateSetOutOfStock(Item $subject, $result, int $websiteId) + { + $this->priceIndexProcessor->markIndexerAsInvalid(); + } + + /** + * @param Item $subject + * @param mixed $result + * @param int $websiteId + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterUpdateSetInStock(Item $subject, $result, int $websiteId) + { + $this->priceIndexProcessor->markIndexerAsInvalid(); + } +} diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php index f1dc9715fd247..53f00529b9bcc 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php @@ -119,7 +119,7 @@ protected function _construct() * @param int $websiteId * @return array */ - public function lockProductsStock(array $productIds, int $websiteId) + public function lockProductsStock(array $productIds, $websiteId) { if (empty($productIds)) { return []; @@ -206,6 +206,8 @@ protected function _initConfig() /** * Set items out of stock basing on their quantities and config settings * + * @deprecated + * @see \Magento\CatalogInventory\Model\ResourceModel\Stock\Item::updateSetOutOfStock * @param string|int $website * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return void @@ -241,6 +243,8 @@ public function updateSetOutOfStock($website = null) /** * Set items in stock basing on their quantities and config settings * + * @deprecated + * @see \Magento\CatalogInventory\Model\ResourceModel\Stock\Item::updateSetInStock * @param int|string $website * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return void @@ -274,6 +278,8 @@ public function updateSetInStock($website) /** * Update items low stock date basing on their quantities and config settings * + * @deprecated + * @see \Magento\CatalogInventory\Model\ResourceModel\Stock\Item::updateLowStockDate * @param int|string $website * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return void diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php index 895fffaa4f80b..ce8930ad4f7a6 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php @@ -6,10 +6,14 @@ namespace Magento\CatalogInventory\Model\ResourceModel\Stock; use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\CatalogInventory\Model\Stock; use Magento\CatalogInventory\Model\Indexer\Stock\Processor; -use Magento\Framework\App\ResourceConnection as AppResource; use Magento\Framework\Model\AbstractModel; -use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Framework\DB\Select; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Stdlib\DateTime\DateTime; /** * Stock item resource model @@ -29,17 +33,36 @@ class Item extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $stockIndexerProcessor; /** - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @var DateTime + */ + private $dateTime; + + /** + * @param Context $context * @param Processor $processor * @param string $connectionName + * @param StockConfigurationInterface $stockConfiguration + * @param DateTime $dateTime */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, + Context $context, Processor $processor, - $connectionName = null + $connectionName = null, + StockConfigurationInterface $stockConfiguration = null, + DateTime $dateTime = null ) { $this->stockIndexerProcessor = $processor; parent::__construct($context, $connectionName); + + $this->stockConfiguration = $stockConfiguration ?? + ObjectManager::getInstance()->get(StockConfigurationInterface::class); + $this->dateTime = $dateTime ?? + ObjectManager::getInstance()->get(DateTime::class); } /** @@ -139,4 +162,160 @@ public function setProcessIndexEvents($process = true) $this->processIndexEvents = $process; return $this; } + + /** + * Set items out of stock basing on their quantities and config settings + * + * @param int $websiteId + * @return void + */ + public function updateSetOutOfStock(int $websiteId) + { + $connection = $this->getConnection(); + + $values = [ + 'is_in_stock' => Stock::STOCK_OUT_OF_STOCK, + 'stock_status_changed_auto' => 1, + ]; + $select = $this->buildProductsSelectByConfigTypes(); + $where = [ + 'website_id = ' . $websiteId, + 'is_in_stock = ' . Stock::STOCK_IN_STOCK, + '(use_config_manage_stock = 1 AND 1 = ' . $this->stockConfiguration->getManageStock() . ')' + . ' OR (use_config_manage_stock = 0 AND manage_stock = 1)', + '(use_config_min_qty = 1 AND qty <= ' . $this->stockConfiguration->getMinQty() . ')' + . ' OR (use_config_min_qty = 0 AND qty <= min_qty)', + 'product_id IN (' . $select->assemble() . ')', + ]; + $backordersWhere = '(use_config_backorders = 0 AND backorders = ' . Stock::BACKORDERS_NO . ')'; + if (Stock::BACKORDERS_NO == $this->stockConfiguration->getBackorders()) { + $where[] = $backordersWhere . ' OR use_config_backorders = 1'; + } else { + $where[] = $backordersWhere; + } + $connection->update($this->getMainTable(), $values, $where); + + $this->stockIndexerProcessor->markIndexerAsInvalid(); + } + + /** + * Set items in stock basing on their quantities and config settings + * + * @param int $websiteId + * @return void + */ + public function updateSetInStock(int $websiteId) + { + $connection = $this->getConnection(); + + $values = [ + 'is_in_stock' => Stock::STOCK_IN_STOCK, + ]; + $select = $this->buildProductsSelectByConfigTypes(); + $where = [ + 'website_id = ' . $websiteId, + 'stock_status_changed_auto = 1', + '(use_config_min_qty = 1 AND qty > ' . $this->stockConfiguration->getMinQty() . ')' + . ' OR (use_config_min_qty = 0 AND qty > min_qty)', + 'product_id IN (' . $select->assemble() . ')', + ]; + $manageStockWhere = '(use_config_manage_stock = 0 AND manage_stock = 1)'; + if ($this->stockConfiguration->getManageStock()) { + $where[] = $manageStockWhere . ' OR use_config_manage_stock = 1'; + } else { + $where[] = $manageStockWhere; + } + $connection->update($this->getMainTable(), $values, $where); + + $this->stockIndexerProcessor->markIndexerAsInvalid(); + } + + /** + * Update items low stock date basing on their quantities and config settings + * + * @param int $websiteId + * @return void + */ + public function updateLowStockDate(int $websiteId) + { + $connection = $this->getConnection(); + + $condition = $connection->quoteInto( + '(use_config_notify_stock_qty = 1 AND qty < ?)', + $this->stockConfiguration->getNotifyStockQty() + ) . ' OR (use_config_notify_stock_qty = 0 AND qty < notify_stock_qty)'; + $currentDbTime = $connection->quoteInto('?', $this->dateTime->gmtDate()); + $conditionalDate = $connection->getCheckSql($condition, $currentDbTime, 'NULL'); + $value = [ + 'low_stock_date' => new \Zend_Db_Expr($conditionalDate), + ]; + $select = $this->buildProductsSelectByConfigTypes(); + $where = [ + 'website_id = ' . $websiteId, + 'product_id IN (' . $select->assemble() . ')' + ]; + $manageStockWhere = '(use_config_manage_stock = 0 AND manage_stock = 1)'; + if ($this->stockConfiguration->getManageStock()) { + $where[] = $manageStockWhere . ' OR use_config_manage_stock = 1'; + } else { + $where[] = $manageStockWhere; + } + $connection->update($this->getMainTable(), $value, $where); + } + + public function getManageStockExpr(string $tableAlias = ''): \Zend_Db_Expr + { + if ($tableAlias) { + $tableAlias .= '.'; + } + $manageStock = $this->getConnection()->getCheckSql( + $tableAlias . 'use_config_manage_stock = 1', + $this->stockConfiguration->getManageStock(), + $tableAlias . 'manage_stock' + ); + + return $manageStock; + } + + public function getBackordersExpr(string $tableAlias = ''): \Zend_Db_Expr + { + if ($tableAlias) { + $tableAlias .= '.'; + } + $itemBackorders = $this->getConnection()->getCheckSql( + $tableAlias . 'use_config_backorders = 1', + $this->stockConfiguration->getBackorders(), + $tableAlias . 'backorders' + ); + + return $itemBackorders; + } + + public function getMinSaleQtyExpr(string $tableAlias = ''): \Zend_Db_Expr + { + if ($tableAlias) { + $tableAlias .= '.'; + } + $itemMinSaleQty = $this->getConnection()->getCheckSql( + $tableAlias . 'use_config_min_sale_qty = 1', + $this->stockConfiguration->getMinSaleQty(), + $tableAlias . 'min_sale_qty' + ); + + return $itemMinSaleQty; + } + + /** + * Build select for products with types from config + * + * @return Select + */ + private function buildProductsSelectByConfigTypes(): Select + { + $select = $this->getConnection()->select() + ->from($this->getTable('catalog_product_entity'), 'entity_id') + ->where('type_id IN (?)', array_keys($this->stockConfiguration->getIsQtyTypeIds(true))); + + return $select; + } } diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index ea4fdc994d682..fb6fc3be61375 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -160,7 +160,7 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ } if (!$this->checkQty($stockItem, $summaryQty) || !$this->checkQty($stockItem, $qty)) { - $message = __('We don\'t have as many "%1" as you requested.', $stockItem->getProductName()); + $message = __('The requested qty is not available'); $result->setHasError(true)->setMessage($message)->setQuoteMessage($message)->setQuoteMessageIndex('qty'); return $result; } else { @@ -212,7 +212,7 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ } } elseif ($stockItem->getShowDefaultNotificationMessage()) { $result->setMessage( - __('We don\'t have as many "%1" as you requested.', $stockItem->getProductName()) + __('The requested qty is not available') ); } } diff --git a/app/code/Magento/CatalogInventory/Observer/InvalidatePriceIndexUponConfigChangeObserver.php b/app/code/Magento/CatalogInventory/Observer/InvalidatePriceIndexUponConfigChangeObserver.php new file mode 100644 index 0000000000000..976110ec76cf3 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Observer/InvalidatePriceIndexUponConfigChangeObserver.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Observer; + +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Event\Observer; +use Magento\CatalogInventory\Model\Configuration; +use Magento\Catalog\Model\Indexer\Product\Price\Processor; + +/** + * Catalog inventory config changes module observer. + */ +class InvalidatePriceIndexUponConfigChangeObserver implements ObserverInterface +{ + /** + * @var Processor + */ + private $priceIndexProcessor; + + /** + * @param Processor $priceIndexProcessor + */ + public function __construct(Processor $priceIndexProcessor) + { + $this->priceIndexProcessor = $priceIndexProcessor; + } + + /** + * Invalidate product price index on catalog inventory config changes. + * + * @param Observer $observer + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute(Observer $observer) + { + $changedPaths = (array) $observer->getEvent()->getChangedPaths(); + + if (\in_array(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, $changedPaths, true)) { + $priceIndexer = $this->priceIndexProcessor->getIndexer(); + $priceIndexer->invalidate(); + } + } +} diff --git a/app/code/Magento/CatalogInventory/Observer/UpdateItemsStockUponConfigChangeObserver.php b/app/code/Magento/CatalogInventory/Observer/UpdateItemsStockUponConfigChangeObserver.php index 47ee6512920d8..21c78eca93695 100644 --- a/app/code/Magento/CatalogInventory/Observer/UpdateItemsStockUponConfigChangeObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/UpdateItemsStockUponConfigChangeObserver.php @@ -3,11 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Observer; use Magento\Framework\Event\Observer as EventObserver; use Magento\Framework\Event\ObserverInterface; +use Magento\CatalogInventory\Model\Configuration; +use Magento\CatalogInventory\Model\ResourceModel\Stock\Item; /** * Catalog inventory module observer @@ -15,16 +16,16 @@ class UpdateItemsStockUponConfigChangeObserver implements ObserverInterface { /** - * @var \Magento\CatalogInventory\Model\ResourceModel\Stock + * @var Item */ - protected $resourceStock; + protected $resourceStockItem; /** - * @param \Magento\CatalogInventory\Model\ResourceModel\Stock $resourceStock + * @param Item $resourceStockItem */ - public function __construct(\Magento\CatalogInventory\Model\ResourceModel\Stock $resourceStock) + public function __construct(Item $resourceStockItem) { - $this->resourceStock = $resourceStock; + $this->resourceStockItem = $resourceStockItem; } /** @@ -35,9 +36,18 @@ public function __construct(\Magento\CatalogInventory\Model\ResourceModel\Stock */ public function execute(EventObserver $observer) { - $website = $observer->getEvent()->getWebsite(); - $this->resourceStock->updateSetOutOfStock($website); - $this->resourceStock->updateSetInStock($website); - $this->resourceStock->updateLowStockDate($website); + $website = (int) $observer->getEvent()->getWebsite(); + $changedPaths = (array) $observer->getEvent()->getChangedPaths(); + + if (\array_intersect([ + Configuration::XML_PATH_MANAGE_STOCK, + Configuration::XML_PATH_MIN_QTY, + Configuration::XML_PATH_BACKORDERS, + Configuration::XML_PATH_NOTIFY_STOCK_QTY, + ], $changedPaths)) { + $this->resourceStockItem->updateSetOutOfStock($website); + $this->resourceStockItem->updateSetInStock($website); + $this->resourceStockItem->updateLowStockDate($website); + } } } diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml index 1bec4cc99c0e8..c7c9126f46803 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/DisplayOutOfStockProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="displayOutOfStockProduct"> <amOnPage url="{{InventoryConfigurationPage.url}}" stepKey="navigateToInventoryConfigurationPage"/> <waitForPageLoad stepKey="waitForConfigPageToLoad"/> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Page/InventoryConfigurationPage.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Page/InventoryConfigurationPage.xml index 95e873a3b164d..ba8a3c300b2e8 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Page/InventoryConfigurationPage.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Page/InventoryConfigurationPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="InventoryConfigurationPage" url="admin/system_config/edit/section/cataloginventory/" area="admin" module="Magento_Config"> <section name="InventorySection"/> </page> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml index 55fbc84ead96a..929f43467b947 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/InventorySection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="InventoryConfigSection"> <element name="ProductStockOptionsTab" type="button" selector="#cataloginventory_options-head"/> <element name="CheckIfProductStockOptionsTabExpanded" type="button" selector="#cataloginventory_options-head:not(.open)"/> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/composer.json b/app/code/Magento/CatalogInventory/Test/Mftf/composer.json deleted file mode 100644 index afd8d3ddb661a..0000000000000 --- a/app/code/Magento/CatalogInventory/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-inventory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Observer/UpdateItemsStockUponConfigChangeObserverTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Observer/UpdateItemsStockUponConfigChangeObserverTest.php index 70a179b484379..7b82b5927d22c 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Observer/UpdateItemsStockUponConfigChangeObserverTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Observer/UpdateItemsStockUponConfigChangeObserverTest.php @@ -15,9 +15,9 @@ class UpdateItemsStockUponConfigChangeObserverTest extends \PHPUnit\Framework\Te protected $observer; /** - * @var \Magento\CatalogInventory\Model\ResourceModel\Stock|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\CatalogInventory\Model\ResourceModel\Stock\Item|\PHPUnit_Framework_MockObject_MockObject */ - protected $resourceStock; + protected $resourceStockItem; /** * @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject @@ -31,11 +31,11 @@ class UpdateItemsStockUponConfigChangeObserverTest extends \PHPUnit\Framework\Te protected function setUp() { - $this->resourceStock = $this->createMock(\Magento\CatalogInventory\Model\ResourceModel\Stock::class); + $this->resourceStockItem = $this->createMock(\Magento\CatalogInventory\Model\ResourceModel\Stock\Item::class); $this->event = $this->getMockBuilder(\Magento\Framework\Event::class) ->disableOriginalConstructor() - ->setMethods(['getWebsite']) + ->setMethods(['getWebsite', 'getChangedPaths']) ->getMock(); $this->eventObserver = $this->getMockBuilder(\Magento\Framework\Event\Observer::class) @@ -50,7 +50,7 @@ protected function setUp() $this->observer = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( \Magento\CatalogInventory\Observer\UpdateItemsStockUponConfigChangeObserver::class, [ - 'resourceStock' => $this->resourceStock, + 'resourceStockItem' => $this->resourceStockItem, ] ); } @@ -58,13 +58,16 @@ protected function setUp() public function testUpdateItemsStockUponConfigChange() { $websiteId = 1; - $this->resourceStock->expects($this->once())->method('updateSetOutOfStock'); - $this->resourceStock->expects($this->once())->method('updateSetInStock'); - $this->resourceStock->expects($this->once())->method('updateLowStockDate'); + $this->resourceStockItem->expects($this->once())->method('updateSetOutOfStock'); + $this->resourceStockItem->expects($this->once())->method('updateSetInStock'); + $this->resourceStockItem->expects($this->once())->method('updateLowStockDate'); $this->event->expects($this->once()) ->method('getWebsite') ->will($this->returnValue($websiteId)); + $this->event->expects($this->once()) + ->method('getChangedPaths') + ->will($this->returnValue([\Magento\CatalogInventory\Model\Configuration::XML_PATH_MANAGE_STOCK])); $this->observer->execute($this->eventObserver); } diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml index b9332575c96f7..08ed0a8f49470 100644 --- a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml @@ -41,13 +41,13 @@ <![CDATA[Please note that these settings apply to individual items in the cart, not to the entire cart.]]> </comment> <label>Product Stock Options</label> - <field id="manage_stock" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="manage_stock" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Manage Stock</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <backend_model>Magento\CatalogInventory\Model\Config\Backend\Managestock</backend_model> <comment>Changing can take some time due to processing whole catalog.</comment> </field> - <field id="backorders" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="backorders" translate="label comment" type="select" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Backorders</label> <source_model>Magento\CatalogInventory\Model\Source\Backorders</source_model> <backend_model>Magento\CatalogInventory\Model\Config\Backend\Backorders</backend_model> diff --git a/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json b/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json index 6ae1851d7e94e..2580ec1e336f1 100644 --- a/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json +++ b/app/code/Magento/CatalogInventory/etc/db_schema_whitelist.json @@ -1,126 +1,126 @@ { - "cataloginventory_stock": { - "column": { - "stock_id": true, - "website_id": true, - "stock_name": true + "cataloginventory_stock": { + "column": { + "stock_id": true, + "website_id": true, + "stock_name": true + }, + "index": { + "CATALOGINVENTORY_STOCK_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "CATALOGINVENTORY_STOCK_WEBSITE_ID": true + "cataloginventory_stock_item": { + "column": { + "item_id": true, + "product_id": true, + "stock_id": true, + "qty": true, + "min_qty": true, + "use_config_min_qty": true, + "is_qty_decimal": true, + "backorders": true, + "use_config_backorders": true, + "min_sale_qty": true, + "use_config_min_sale_qty": true, + "max_sale_qty": true, + "use_config_max_sale_qty": true, + "is_in_stock": true, + "low_stock_date": true, + "notify_stock_qty": true, + "use_config_notify_stock_qty": true, + "manage_stock": true, + "use_config_manage_stock": true, + "stock_status_changed_auto": true, + "use_config_qty_increments": true, + "qty_increments": true, + "use_config_enable_qty_inc": true, + "enable_qty_increments": true, + "is_decimal_divided": true, + "website_id": true + }, + "index": { + "CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID": true, + "CATALOGINVENTORY_STOCK_ITEM_STOCK_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATINV_STOCK_ITEM_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATINV_STOCK_ITEM_STOCK_ID_CATINV_STOCK_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_ITEM_PRODUCT_ID_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_ITEM_PRODUCT_ID_WEBSITE_ID": true, + "CATINV_STOCK_ITEM_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "cataloginventory_stock_item": { - "column": { - "item_id": true, - "product_id": true, - "stock_id": true, - "qty": true, - "min_qty": true, - "use_config_min_qty": true, - "is_qty_decimal": true, - "backorders": true, - "use_config_backorders": true, - "min_sale_qty": true, - "use_config_min_sale_qty": true, - "max_sale_qty": true, - "use_config_max_sale_qty": true, - "is_in_stock": true, - "low_stock_date": true, - "notify_stock_qty": true, - "use_config_notify_stock_qty": true, - "manage_stock": true, - "use_config_manage_stock": true, - "stock_status_changed_auto": true, - "use_config_qty_increments": true, - "qty_increments": true, - "use_config_enable_qty_inc": true, - "enable_qty_increments": true, - "is_decimal_divided": true, - "website_id": true - }, - "index": { - "CATALOGINVENTORY_STOCK_ITEM_WEBSITE_ID": true, - "CATALOGINVENTORY_STOCK_ITEM_STOCK_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATINV_STOCK_ITEM_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATINV_STOCK_ITEM_STOCK_ID_CATINV_STOCK_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_ITEM_PRODUCT_ID_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_ITEM_PRODUCT_ID_WEBSITE_ID": true, - "CATINV_STOCK_ITEM_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "cataloginventory_stock_status": { - "column": { - "product_id": true, - "website_id": true, - "stock_id": true, - "qty": true, - "stock_status": true - }, - "index": { - "CATALOGINVENTORY_STOCK_STATUS_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_WEBSITE_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_STOCK_STATUS": true - }, - "constraint": { - "PRIMARY": true - } - }, - "cataloginventory_stock_status_idx": { - "column": { - "product_id": true, - "website_id": true, - "stock_id": true, - "qty": true, - "stock_status": true - }, - "index": { - "CATALOGINVENTORY_STOCK_STATUS_IDX_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_IDX_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true - } - }, - "cataloginventory_stock_status_tmp": { - "column": { - "product_id": true, - "website_id": true, - "stock_id": true, - "qty": true, - "stock_status": true - }, - "index": { - "CATALOGINVENTORY_STOCK_STATUS_TMP_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_TMP_WEBSITE_ID": true + "cataloginventory_stock_status": { + "column": { + "product_id": true, + "website_id": true, + "stock_id": true, + "qty": true, + "stock_status": true + }, + "index": { + "CATALOGINVENTORY_STOCK_STATUS_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_WEBSITE_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_STOCK_STATUS": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "cataloginventory_stock_status_replica": { - "column": { - "product_id": true, - "website_id": true, - "stock_id": true, - "qty": true, - "stock_status": true + "cataloginventory_stock_status_idx": { + "column": { + "product_id": true, + "website_id": true, + "stock_id": true, + "qty": true, + "stock_status": true + }, + "index": { + "CATALOGINVENTORY_STOCK_STATUS_IDX_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_IDX_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "CATALOGINVENTORY_STOCK_STATUS_REPLICA_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_REPLICA_WEBSITE_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_REPLICA_STOCK_STATUS": true, - "CATALOGINVENTORY_STOCK_STATUS_STOCK_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_WEBSITE_ID": true, - "CATALOGINVENTORY_STOCK_STATUS_STOCK_STATUS": true + "cataloginventory_stock_status_tmp": { + "column": { + "product_id": true, + "website_id": true, + "stock_id": true, + "qty": true, + "stock_status": true + }, + "index": { + "CATALOGINVENTORY_STOCK_STATUS_TMP_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_TMP_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true + "cataloginventory_stock_status_replica": { + "column": { + "product_id": true, + "website_id": true, + "stock_id": true, + "qty": true, + "stock_status": true + }, + "index": { + "CATALOGINVENTORY_STOCK_STATUS_REPLICA_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_REPLICA_WEBSITE_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_REPLICA_STOCK_STATUS": true, + "CATALOGINVENTORY_STOCK_STATUS_STOCK_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_WEBSITE_ID": true, + "CATALOGINVENTORY_STOCK_STATUS_STOCK_STATUS": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml index 65bc277121429..912ab48d28824 100644 --- a/app/code/Magento/CatalogInventory/etc/di.xml +++ b/app/code/Magento/CatalogInventory/etc/di.xml @@ -118,4 +118,21 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface"> + <arguments> + <argument name="priceModifiers" xsi:type="array"> + <item name="inventoryProductPriceIndexFilter" xsi:type="object">Magento\CatalogInventory\Model\Indexer\ProductPriceIndexFilter</item> + </argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier"> + <arguments> + <argument name="priceModifiers" xsi:type="array"> + <item name="inventoryProductPriceIndexFilter" xsi:type="object">Magento\CatalogInventory\Model\Indexer\ProductPriceIndexFilter</item> + </argument> + </arguments> + </type> + <type name="Magento\CatalogInventory\Model\ResourceModel\Stock\Item"> + <plugin name="priceIndexUpdater" type="Magento\CatalogInventory\Model\Plugin\PriceIndexUpdater" /> + </type> </config> diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 3197501e9b70b..328edbade6068 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -38,6 +38,7 @@ </event> <event name="admin_system_config_changed_section_cataloginventory"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\UpdateItemsStockUponConfigChangeObserver"/> + <observer name="invalidatePriceIndex" instance="Magento\CatalogInventory\Observer\InvalidatePriceIndexUponConfigChangeObserver"/> </event> <event name="sales_quote_item_collection_products_after_load"> <observer name="add_stock_items" instance="Magento\CatalogInventory\Observer\AddStockItemsObserver"/> diff --git a/app/code/Magento/CatalogInventory/etc/mview.xml b/app/code/Magento/CatalogInventory/etc/mview.xml index c3d73ff43e8eb..72dda16e8b5bb 100644 --- a/app/code/Magento/CatalogInventory/etc/mview.xml +++ b/app/code/Magento/CatalogInventory/etc/mview.xml @@ -11,4 +11,9 @@ <table name="cataloginventory_stock_item" entity_column="product_id" /> </subscriptions> </view> + <view id="catalog_product_price" class="Magento\Catalog\Model\Indexer\Product\Price" group="indexer"> + <subscriptions> + <table name="cataloginventory_stock_item" entity_column="product_id" /> + </subscriptions> + </view> </config> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index d82b4a97ddbf6..f8571b6e332b6 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -571,6 +571,7 @@ <settings> <scopeLabel>[GLOBAL]</scopeLabel> <validation> + <rule name="validate-integer" xsi:type="boolean">true</rule> <rule name="validate-number" xsi:type="boolean">true</rule> </validation> <label translate="true">Qty Increments</label> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js index 23a33f51af6d4..75d684137a28b 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js +++ b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js @@ -20,7 +20,6 @@ define([ var isDigits = value !== 1; this.validation['validate-integer'] = isDigits; - this.validation['validate-digits'] = isDigits; this.validation['less-than-equals-to'] = isDigits ? 99999999 : 99999999.9999; this.validate(); } diff --git a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php index 0b4748c933a3c..9d1a23611dc27 100644 --- a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php +++ b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Catalog/Edit/Tab/Conditions.php @@ -66,7 +66,7 @@ public function getTabTitle() } /** - * Returns status flag about this tab can be showen or not + * Returns status flag about this tab can be shown or not * * @return bool * @codeCoverageIgnore diff --git a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php index 306d3b9a347b4..87cb18253a107 100644 --- a/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php +++ b/app/code/Magento/CatalogRule/Block/Adminhtml/Promo/Widget/Chooser/Sku.php @@ -139,7 +139,7 @@ protected function _getCpCollectionInstance() } /** - * Define Cooser Grid Columns and filters + * Define Chooser Grid Columns and filters * * @return $this */ diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/ApplyRules.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/ApplyRules.php index 85ad74f7bbfe2..4badfa1219e10 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/ApplyRules.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/ApplyRules.php @@ -25,14 +25,14 @@ public function execute() $ruleJob->applyAll(); if ($ruleJob->hasSuccess()) { - $this->messageManager->addSuccess($ruleJob->getSuccess()); + $this->messageManager->addSuccessMessage($ruleJob->getSuccess()); $this->_objectManager->create(\Magento\CatalogRule\Model\Flag::class)->loadSelf()->setState(0)->save(); } elseif ($ruleJob->hasError()) { - $this->messageManager->addError($errorMessage . ' ' . $ruleJob->getError()); + $this->messageManager->addErrorMessage($errorMessage . ' ' . $ruleJob->getError()); } } catch (\Exception $e) { $this->_objectManager->create(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError($errorMessage); + $this->messageManager->addErrorMessage($errorMessage); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php index 8b007031f3305..3500506d8d6c5 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Delete.php @@ -25,13 +25,13 @@ public function execute() $ruleRepository->deleteById($id); $this->_objectManager->create(\Magento\CatalogRule\Model\Flag::class)->loadSelf()->setState(1)->save(); - $this->messageManager->addSuccess(__('You deleted the rule.')); + $this->messageManager->addSuccessMessage(__('You deleted the rule.')); $this->_redirect('catalog_rule/*/'); return; } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('We can\'t delete this rule right now. Please review the log and try again.') ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); @@ -39,7 +39,7 @@ public function execute() return; } } - $this->messageManager->addError(__('We can\'t find a rule to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a rule to delete.')); $this->_redirect('catalog_rule/*/'); } } diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php index 97a5693b18117..945c28b2088f2 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Edit.php @@ -24,7 +24,7 @@ public function execute() try { $model = $ruleRepository->get($id); } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { - $this->messageManager->addError(__('This rule no longer exists.')); + $this->messageManager->addErrorMessage(__('This rule no longer exists.')); $this->_redirect('catalog_rule/*'); return; } diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php index 0170fc76b6aab..f3046c58a389b 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php @@ -68,7 +68,7 @@ public function execute() $validateResult = $model->validateData(new \Magento\Framework\DataObject($data)); if ($validateResult !== true) { foreach ($validateResult as $errorMessage) { - $this->messageManager->addError($errorMessage); + $this->messageManager->addErrorMessage($errorMessage); } $this->_getSession()->setPageData($data); $this->dataPersistor->set('catalog_rule', $data); @@ -88,7 +88,7 @@ public function execute() $ruleRepository->save($model); - $this->messageManager->addSuccess(__('You saved the rule.')); + $this->messageManager->addSuccessMessage(__('You saved the rule.')); $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setPageData(false); $this->dataPersistor->clear('catalog_rule'); @@ -111,9 +111,9 @@ public function execute() } return; } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Something went wrong while saving the rule data. Please review the error log.') ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php b/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php index a60b05dc7c9bc..404fc32e0c0d4 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php @@ -10,6 +10,8 @@ use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceModifierInterface; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; use Magento\CatalogRule\Model\ResourceModel\Rule\Product\Price; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\App\ObjectManager; /** * Class for adding catalog rule prices to price index table. @@ -21,12 +23,29 @@ class ProductPriceIndexModifier implements PriceModifierInterface */ private $priceResourceModel; + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var string + */ + private $connectionName; + /** * @param Price $priceResourceModel + * @param ResourceConnection $resourceConnection + * @param string $connectionName */ - public function __construct(Price $priceResourceModel) - { + public function __construct( + Price $priceResourceModel, + ResourceConnection $resourceConnection, + $connectionName = 'indexer' + ) { $this->priceResourceModel = $priceResourceModel; + $this->resourceConnection = $resourceConnection ?: ObjectManager::getInstance()->get(ResourceConnection::class); + $this->connectionName = $connectionName; } /** @@ -34,7 +53,8 @@ public function __construct(Price $priceResourceModel) */ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void { - $connection = $this->priceResourceModel->getConnection(); + $connection = $this->resourceConnection->getConnection($this->connectionName); + $select = $connection->select(); $select->join( diff --git a/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php index 51cb6638af48b..ab650c94a0f08 100644 --- a/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php @@ -99,6 +99,10 @@ protected function _prepareDatetimeValue($value, \Magento\Framework\Model\Abstra { $attribute = $model->getResource()->getAttribute($this->getAttribute()); if ($attribute && $attribute->getBackendType() == 'datetime') { + if (!$value) { + return null; + } + $this->setValue(strtotime($this->getValue())); $value = strtotime($value); } diff --git a/app/code/Magento/CatalogRule/Model/Rule/Job.php b/app/code/Magento/CatalogRule/Model/Rule/Job.php index 63ff98d4ca5b7..71734eb3c5d46 100644 --- a/app/code/Magento/CatalogRule/Model/Rule/Job.php +++ b/app/code/Magento/CatalogRule/Model/Rule/Job.php @@ -8,6 +8,10 @@ * See COPYING.txt for license details. */ +namespace Magento\CatalogRule\Model\Rule; + +use Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor; + /** * Catalog Rule job model * @@ -18,13 +22,8 @@ * @method bool hasSuccess() * @method bool hasError() * - * @author Magento Core Team <core@magentocommerce.com> - */ -namespace Magento\CatalogRule\Model\Rule; - -use Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor; - -/** + * @author Magento Core Team <core@magentocommerce.com> + * * @api * @since 100.0.2 */ @@ -39,10 +38,14 @@ class Job extends \Magento\Framework\DataObject * Basic object initialization * * @param RuleProductProcessor $ruleProcessor + * @param array $data */ - public function __construct(RuleProductProcessor $ruleProcessor) - { + public function __construct( + RuleProductProcessor $ruleProcessor, + array $data = [] + ) { $this->ruleProcessor = $ruleProcessor; + parent::__construct($data); } /** diff --git a/app/code/Magento/CatalogRule/Observer/AddDirtyRulesNotice.php b/app/code/Magento/CatalogRule/Observer/AddDirtyRulesNotice.php index 08c3d97b216ed..749ac3cf51249 100644 --- a/app/code/Magento/CatalogRule/Observer/AddDirtyRulesNotice.php +++ b/app/code/Magento/CatalogRule/Observer/AddDirtyRulesNotice.php @@ -37,7 +37,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) $dirtyRules = $observer->getData('dirty_rules'); if (!empty($dirtyRules)) { if ($dirtyRules->getState()) { - $this->messageManager->addNotice($observer->getData('message')); + $this->messageManager->addNoticeMessage($observer->getData('message')); } } } diff --git a/app/code/Magento/CatalogRule/Plugin/Indexer/Product/Attribute.php b/app/code/Magento/CatalogRule/Plugin/Indexer/Product/Attribute.php index cc808a38db698..7fdffe933db8c 100644 --- a/app/code/Magento/CatalogRule/Plugin/Indexer/Product/Attribute.php +++ b/app/code/Magento/CatalogRule/Plugin/Indexer/Product/Attribute.php @@ -103,7 +103,7 @@ protected function checkCatalogRulesAvailability($attributeCode) if ($disabledRulesCount) { $this->ruleProductProcessor->markIndexerAsInvalid(); - $this->messageManager->addWarning( + $this->messageManager->addWarningMessage( __( 'You disabled %1 Catalog Price Rules based on "%2" attribute.', $disabledRulesCount, diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml index ad92af9bb3b1b..bfc059ccb247b 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- action group to create a new catalog price rule giving a catalogRule entity --> <actionGroup name="newCatalogPriceRuleByUI"> <arguments> @@ -17,12 +17,12 @@ <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> <waitForPageLoad stepKey="waitForPriceRulePage"/> <click stepKey="addNewRule" selector="{{AdminGridMainControls.add}}"/> + <waitForPageLoad stepKey="waitForIndividualRulePage"/> - <!-- Fill the form according the the attributes of the entity --> + <!-- Fill the form according the attributes of the entity --> <fillField stepKey="fillName" selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{catalogRule.name}}"/> <fillField stepKey="fillDescription" selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{catalogRule.description}}"/> <selectOption stepKey="selectSite" selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{catalogRule.website_ids[0]}}"/> - <selectOption stepKey="selectCustomerGroups" selector="{{AdminNewCatalogPriceRule.customerGroups}}" userInput="{{catalogRule.customer_group_ids[0]}}"/> <click stepKey="clickFromCalender" selector="{{AdminNewCatalogPriceRule.fromDateButton}}"/> <click stepKey="clickFromToday" selector="{{AdminNewCatalogPriceRule.todayDate}}"/> <click stepKey="clickToCalender" selector="{{AdminNewCatalogPriceRule.toDateButton}}"/> @@ -30,11 +30,14 @@ <click stepKey="openActionDropdown" selector="{{AdminNewCatalogPriceRule.actionsTab}}"/> <selectOption stepKey="discountType" selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{catalogRule.simple_action}}"/> <fillField stepKey="fillDiscountValue" selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="{{catalogRule.discount_amount}}"/> + <selectOption stepKey="discardSubsequentRules" selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes"/> <!-- Scroll to top and either save or save and apply after the action group --> <scrollToTopOfPage stepKey="scrollToTop"/> + <waitForPageLoad stepKey="waitForApplied"/> </actionGroup> + <!-- Apply all of the saved catalog price rules --> <actionGroup name="applyCatalogPriceRules"> <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> <waitForPageLoad stepKey="waitForPriceRulePage"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml index 3199a53026a92..b1cb1920f39f4 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultCatalogRule" type="catalogRule"> <data key="name" unique="suffix">CatalogPriceRule</data> <data key="description">Catalog Price Rule Description</data> @@ -33,7 +33,7 @@ <item>1</item> </array> <data key="simple_action">by_fixed</data> - <data key="discount_amount">10</data> + <data key="discount_amount">12.3</data> </entity> <entity name="CatalogRuleToPercent" type="catalogRule"> @@ -61,6 +61,6 @@ <item>1</item> </array> <data key="simple_action">to_fixed</data> - <data key="discount_amount">100</data> + <data key="discount_amount">110.7</data> </entity> </entities> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Metadata/catalog-rule-meta.xml b/app/code/Magento/CatalogRule/Test/Mftf/Metadata/catalog-rule-meta.xml index 6f5bd2decc6ce..0d89c7970b852 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Metadata/catalog-rule-meta.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Metadata/catalog-rule-meta.xml @@ -8,7 +8,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="createCatalogRule" dataType="catalogRule" type="create" auth="adminFormKey" url="/catalog_rule/promo_catalog/save/" method="POST"> <contentType>application/x-www-form-urlencoded</contentType> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRulePage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRulePage.xml index e080a252e7855..511a9ac0615d5 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRulePage.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/CatalogRulePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CatalogRulePage" url="catalog_rule/promo_catalog/" module="Magento_CatalogRule" area="admin"> <section name="AdminSecondaryGridSection"/> </page> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleStagingSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleStagingSection.xml new file mode 100644 index 0000000000000..bab9842caaa42 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleStagingSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleStagingSection"> + <element name="status" type="select" selector=".modal-component [data-index='is_active'] select"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml index b60a4c6516a82..ab2c6eb89d266 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml @@ -7,10 +7,11 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewCatalogPriceRule"> <element name="saveAndApply" type="button" selector="#save_and_apply" timeout="30"/> <element name="save" type="button" selector="#save" timeout="30"/> + <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> <element name="ruleName" type="input" selector="[name='name']"/> @@ -33,7 +34,15 @@ <section name="AdminNewCatalogPriceRuleActions"> <element name="apply" type="select" selector="[name='simple_action']"/> <element name="discountAmount" type="input" selector="[name='discount_amount']"/> - <element name="disgardRules" type="select" selector="[name='stop_rules_processing']"/> + <element name="disregardRules" type="select" selector="[name='stop_rules_processing']"/> + </section> + + <section name="AdminNewCatalogPriceRuleConditions"> + <element name="newCondition" type="button" selector="span.rule-param-new-child"/> + <element name="conditionSelect" type="select" selector="select#conditions__{{var}}__new_child" parameterized="true"/> + <element name="targetEllipsis" type="button" selector="//li[{{var}}]//a[@class='label'][text() = '...']" parameterized="true"/> + <element name="targetInput" type="input" selector="input#conditions__{{var1}}--{{var2}}__value" parameterized="true"/> + <element name="applyButton" type="button" selector="#conditions__{{var1}}__children li:nth-of-type({{var2}}) a.rule-param-apply" parameterized="true"/> </section> <section name="AdminCatalogPriceRuleGrid"> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml new file mode 100644 index 0000000000000..741da96179b8c --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminApplyCatalogRuleByCategoryTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog rule by category"/> + <description value="Admin should be able to apply the catalog rule by category"/> + <severity value="MAJOR"/> + <testCaseId value="MC-74"/> + <group value="CatalogRule"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategoryOne"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProductOne"> + <requiredEntity createDataKey="createCategoryOne"/> + </createData> + <createData entity="ApiCategory" stepKey="createCategoryTwo"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProductTwo"> + <requiredEntity createDataKey="createCategoryTwo"/> + </createData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createCategoryOne" stepKey="deleteCategoryOne"/> + <deleteData createDataKey="createSimpleProductOne" stepKey="deleteSimpleProductOne"/> + <deleteData createDataKey="createCategoryTwo" stepKey="deleteCategoryTwo"/> + <deleteData createDataKey="createSimpleProductTwo" stepKey="deleteSimpleProductTwo"/> + + <!-- Delete the catalog price rule --> + <amOnPage stepKey="goToPriceRulePage" url="{{CatalogRulePage.url}}"/> + <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + + <!-- 1. Begin creating a new catalog price rule --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <waitForPageLoad stepKey="waitForPriceRulePage"/> + <click selector="{{AdminGridMainControls.add}}" stepKey="addNewRule"/> + <waitForPageLoad stepKey="waitForIndividualRulePage"/> + <fillField selector="{{AdminNewCatalogPriceRule.ruleName}}" userInput="{{_defaultCatalogRule.name}}" stepKey="fillName"/> + <fillField selector="{{AdminNewCatalogPriceRule.description}}" userInput="{{_defaultCatalogRule.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminNewCatalogPriceRule.websites}}" userInput="{{_defaultCatalogRule.website_ids[0]}}" stepKey="selectSite"/> + <click selector="{{AdminNewCatalogPriceRule.fromDateButton}}" stepKey="clickFromCalender"/> + <click selector="{{AdminNewCatalogPriceRule.todayDate}}" stepKey="clickFromToday"/> + <click selector="{{AdminNewCatalogPriceRule.toDateButton}}" stepKey="clickToCalender"/> + <click selector="{{AdminNewCatalogPriceRule.todayDate}}" stepKey="clickToToday"/> + <click selector="{{AdminNewCatalogPriceRule.conditionsTab}}" stepKey="openConditions"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.newCondition}}" stepKey="clickNewRule"/> + <selectOption selector="{{AdminNewCatalogPriceRuleConditions.conditionSelect('1')}}" userInput="Category" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForEllipsis"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.targetEllipsis('1')}}" stepKey="clickEllipsis"/> + <waitForPageLoad stepKey="waitForInput"/> + + <!-- 2. Fill condition of category = createCategoryOne --> + <fillField selector="{{AdminNewCatalogPriceRuleConditions.targetInput('1', '1')}}" userInput="$$createCategoryOne.id$$" stepKey="fillCategory"/> + <click selector="{{AdminNewCatalogPriceRuleConditions.applyButton('1', '1')}}" stepKey="clickApply"/> + <click selector="{{AdminNewCatalogPriceRule.actionsTab}}" stepKey="openActionDropdown"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.apply}}" userInput="{{_defaultCatalogRule.simple_action}}" stepKey="discountType"/> + <fillField selector="{{AdminNewCatalogPriceRuleActions.discountAmount}}" userInput="50" stepKey="fillDiscountValue"/> + <selectOption selector="{{AdminNewCatalogPriceRuleActions.disregardRules}}" userInput="Yes" stepKey="discardSubsequentRules"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <waitForPageLoad stepKey="waitForApplied"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + + <!-- 3. Save and apply the new catalog price rule --> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- 4. Verify the storefront --> + <amOnPage url="$$createCategoryOne.name$$.html" stepKey="goToCategoryOne"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$$createSimpleProductOne.name$$" stepKey="seeProductOne"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$61.50" stepKey="seeProductOnePrice"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="Regular Price $123.00" stepKey="seeProductOneRegularPrice"/> + <amOnPage url="$$createCategoryTwo.name$$.html" stepKey="goToCategoryTwo"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$$createSimpleProductTwo.name$$" stepKey="seeProductTwo"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$123.00" stepKey="seeProductTwoPrice"/> + <dontSee selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$61.50" stepKey="dontSeeDiscount"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml index a68965d97e879..befe0b0ce7f98 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCatalogPriceRuleByPercentTest"> <annotations> <features value="CatalogRule"/> @@ -28,6 +28,7 @@ <!-- log in and create the price rule --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"/> + <actionGroup stepKey="selectNotLoggedInCustomerGroup" ref="selectNotLoggedInCustomerGroup"/> <click stepKey="saveAndApply" selector="{{AdminNewCatalogPriceRule.saveAndApply}}"/> <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule."/> </before> @@ -64,7 +65,7 @@ <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$110.70"/> </test> - <test name="AdminCreateCatalogPriceRuleByFixedTest"> + <test name="AdminCreateCatalogPriceRuleByFixedTest" extends="AdminCreateCatalogPriceRuleByPercentTest"> <annotations> <features value="CatalogRule"/> <stories value="Create catalog price rule"/> @@ -75,54 +76,19 @@ <group value="CatalogRule"/> </annotations> <before> - <!-- Create the simple product and category that it will be in --> - <createData entity="ApiCategory" stepKey="createCategory"/> - <createData entity="ApiSimpleProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <!-- log in and create the price rule --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> <argument name="catalogRule" value="CatalogRuleByFixed"/> </actionGroup> - <click stepKey="saveAndApply" selector="{{AdminNewCatalogPriceRule.saveAndApply}}"/> - <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule."/> </before> <after> - <!-- delete the simple product and catalog price rule and logout --> - <amOnPage stepKey="goToPriceRulePage" url="admin/catalog_rule/promo_catalog/"/> <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> <argument name="name" value="{{CatalogRuleByFixed.name}}"/> <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> - <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> - - <!-- Go to category page and make sure that all of the prices are correct --> - <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategory"/> - <see stepKey="seeOldPrice" selector="{{StorefrontCategoryProductSection.ProductOldPriceByNumber('1')}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice" selector="{{StorefrontCategoryProductSection.ProductSpecialPriceByNumber('1')}}" userInput="$113.00"/> - - <!-- Go to the simple product page and check that the prices are correct --> - <amOnPage stepKey="goToProductPage" url="$$createProduct.sku$$.html"/> - <waitForPageLoad stepKey="waitForProductPage"/> - <see stepKey="seeOldPriceTag" selector="{{StorefrontProductInfoMainSection.oldPriceTag}}" userInput="Regular Price"/> - <see stepKey="seeOldPrice2" selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice2" selector="{{StorefrontProductInfoMainSection.updatedPrice}}" userInput="$113.00"/> - - <!-- Add the product to cart and check that the price is correct there --> - <click stepKey="addToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> - <waitForPageLoad stepKey="waitForAddedToCart"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> - <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$113.00"/> </test> - <test name="AdminCreateCatalogPriceRuleToPercentTest"> + <test name="AdminCreateCatalogPriceRuleToPercentTest" extends="AdminCreateCatalogPriceRuleByPercentTest"> <annotations> <features value="CatalogRule"/> <stories value="Create catalog price rule"/> @@ -133,54 +99,19 @@ <group value="CatalogRule"/> </annotations> <before> - <!-- Create the simple product and category that it will be in --> - <createData entity="ApiCategory" stepKey="createCategory"/> - <createData entity="ApiSimpleProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <!-- log in and create the price rule --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> <argument name="catalogRule" value="CatalogRuleToPercent"/> </actionGroup> - <click stepKey="saveAndApply" selector="{{AdminNewCatalogPriceRule.saveAndApply}}"/> - <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule."/> </before> <after> - <!-- delete the simple product and catalog price rule and logout --> - <amOnPage stepKey="goToPriceRulePage" url="admin/catalog_rule/promo_catalog/"/> <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> <argument name="name" value="{{CatalogRuleToPercent.name}}"/> <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> - <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> - - <!-- Go to category page and make sure that all of the prices are correct --> - <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategory"/> - <see stepKey="seeOldPrice" selector="{{StorefrontCategoryProductSection.ProductOldPriceByNumber('1')}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice" selector="{{StorefrontCategoryProductSection.ProductSpecialPriceByNumber('1')}}" userInput="$110.70"/> - - <!-- Go to the simple product page and check that the prices are correct --> - <amOnPage stepKey="goToProductPage" url="$$createProduct.sku$$.html"/> - <waitForPageLoad stepKey="waitForProductPage"/> - <see stepKey="seeOldPriceTag" selector="{{StorefrontProductInfoMainSection.oldPriceTag}}" userInput="Regular Price"/> - <see stepKey="seeOldPrice2" selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice2" selector="{{StorefrontProductInfoMainSection.updatedPrice}}" userInput="$110.70"/> - - <!-- Add the product to cart and check that the price is correct there --> - <click stepKey="addToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> - <waitForPageLoad stepKey="waitForAddedToCart"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> - <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$110.70"/> </test> - <test name="AdminCreateCatalogPriceRuleToFixedTest"> + <test name="AdminCreateCatalogPriceRuleToFixedTest" extends="AdminCreateCatalogPriceRuleByPercentTest"> <annotations> <features value="CatalogRule"/> <stories value="Create catalog price rule"/> @@ -191,50 +122,69 @@ <group value="CatalogRule"/> </annotations> <before> - <!-- Create the simple product and category that it will be in --> - <createData entity="ApiCategory" stepKey="createCategory"/> - <createData entity="ApiSimpleProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <!-- log in and create the price rule --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"> <argument name="catalogRule" value="CatalogRuleToFixed"/> </actionGroup> - <click stepKey="saveAndApply" selector="{{AdminNewCatalogPriceRule.saveAndApply}}"/> - <see stepKey="assertSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule."/> </before> <after> - <!-- delete the simple product and catalog price rule and logout --> - <amOnPage stepKey="goToPriceRulePage" url="admin/catalog_rule/promo_catalog/"/> <actionGroup stepKey="deletePriceRule" ref="deleteEntitySecondaryGrid"> <argument name="name" value="{{CatalogRuleToFixed.name}}"/> <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> </actionGroup> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> - <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> + </test> - <!-- Go to category page and make sure that all of the prices are correct --> - <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategory"/> - <see stepKey="seeOldPrice" selector="{{StorefrontCategoryProductSection.ProductOldPriceByNumber('1')}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice" selector="{{StorefrontCategoryProductSection.ProductSpecialPriceByNumber('1')}}" userInput="$100.00"/> - - <!-- Go to the simple product page and check that the prices are correct --> - <amOnPage stepKey="goToProductPage" url="$$createProduct.sku$$.html"/> - <waitForPageLoad stepKey="waitForProductPage"/> - <see stepKey="seeOldPriceTag" selector="{{StorefrontProductInfoMainSection.oldPriceTag}}" userInput="Regular Price"/> - <see stepKey="seeOldPrice2" selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" userInput="$$createProduct.price$$"/> - <see stepKey="seeNewPrice2" selector="{{StorefrontProductInfoMainSection.updatedPrice}}" userInput="$100.00"/> + <test name="AdminCreateCatalogPriceRuleForCustomerGroupTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Apply catalog price rule"/> + <title value="Admin should be able to apply the catalog rule by customer group"/> + <description value="Admin should be able to apply the catalog rule by customer group"/> + <severity value="MAJOR"/> + <testCaseId value="MC-71"/> + <group value="CatalogRule"/> + </annotations> + <before> + <!-- Create a simple product and a category--> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete the simple product and category --> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete the catalog rule --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToRulePage"/> + <waitForPageLoad stepKey="waitForRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRule"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> - <!-- Add the product to cart and check that the price is correct there --> - <click stepKey="addToCart" selector="{{StorefrontProductActionSection.addToCart}}"/> - <waitForPageLoad stepKey="waitForAddedToCart"/> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForCart"/> - <see stepKey="seeNewPriceInCart" selector="{{CheckoutCartSummarySection.subtotal}}" userInput="$100.00"/> + <!-- Create a catalog rule for the NOT LOGGED IN customer group --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createNewPriceRule"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/> + + <!-- As a NOT LOGGED IN user, go to the storefront category page and should see the discount --> + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategory1"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$$createProduct.name$$" stepKey="seeProduct1"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$110.70" stepKey="seeDiscountedPrice1"/> + + <!-- Create a user account --> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="createAnAccount"> + <argument name="Customer" value="CustomerEntityOne"/> + </actionGroup> + + <!-- As a logged in user, go to the storefront category page and should NOT see discount --> + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategory2"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$$createProduct.name$$" stepKey="seeProduct2"/> + <see selector="{{StorefrontCategoryProductSection.ProductInfoByNumber('1')}}" userInput="$123.00" stepKey="seeDiscountedPrice2"/> </test> </tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml new file mode 100644 index 0000000000000..d3546d06492be --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteCatalogPriceRuleTest"> + <annotations> + <features value="CatalogRule"/> + <stories value="Delete Catalog Price Rule"/> + <title value="Admin should be able to delete catalog price rule"/> + <description value="Admin should be able to delete catalog price rule"/> + <severity value="MAJOR"/> + <testCaseId value="MC-160"/> + <group value="CatalogRule"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + + <!-- Create a simple product --> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a configurable product --> + <actionGroup ref="createConfigurableProduct" stepKey="createConfigurableProduct"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + </after> + + <!-- Create a catalog price rule --> + <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createNewPriceRule"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/> + + <!-- Verify that category page shows the discount --> + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryPage1"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(ApiSimpleProduct.name)}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct1"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName(ApiSimpleProduct.name)}}" userInput="$110.70" stepKey="seeSimpleProductDiscount1"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(_defaultProduct.name)}}" userInput="{{_defaultProduct.name}}" stepKey="seeConfigurableProduct1"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName(_defaultProduct.name)}}" userInput="$0.90" stepKey="seeConfigurableProductDiscount1"/> + + <!-- Verify that the simple product page shows the discount --> + <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="goToSimpleProductPage1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$createSimpleProduct.name$$" stepKey="seeCorrectName1"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="$$createSimpleProduct.sku$$" stepKey="seeCorrectSku1"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$110.70" stepKey="seeCorrectPrice1"/> + + <!-- Verify that the configurable product page the catalog price rule discount --> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="goToConfigurableProductPage1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="seeCorrectName2"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="seeCorrectSku2"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$0.90" stepKey="seeCorrectPrice2"/> + + <!-- Delete the rule --> + <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deletePriceRule"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.catalogRuleIdentifierSearch}}"/> + </actionGroup> + + <!-- Apply and flush the cache --> + <click selector="{{AdminCatalogPriceRuleGrid.applyRules}}" stepKey="clickApplyRules"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <!-- Verify that category page shows the original prices --> + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryPage2"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(ApiSimpleProduct.name)}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName(ApiSimpleProduct.name)}}" userInput="$123.00" stepKey="seeSimpleProductDiscount2"/> + <see selector="{{StorefrontCategoryProductSection.ProductTitleByName(_defaultProduct.name)}}" userInput="{{_defaultProduct.name}}" stepKey="seeConfigurableProduct2"/> + <see selector="{{StorefrontCategoryProductSection.ProductPriceByName(_defaultProduct.name)}}" userInput="$1.00" stepKey="seeConfigurableProductDiscount2"/> + + <!-- Verify that the simple product page shows the original price --> + <amOnPage url="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="goToSimpleProductPage2"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$createSimpleProduct.name$$" stepKey="seeCorrectName3"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="$$createSimpleProduct.sku$$" stepKey="seeCorrectSku3"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$123.00" stepKey="seeCorrectPrice3"/> + + <!-- Verify that the configurable product page shows the original price --> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="goToConfigurableProductPage2"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="seeCorrectName4"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="seeCorrectSku4"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$1.00" stepKey="seeCorrectPrice4"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index 77a9a48074c29..e7be6e8443a36 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontInactiveCatalogRuleTest"> <annotations> <features value="CatalogRule"/> @@ -24,7 +24,8 @@ <createData entity="ApiSimpleProduct" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> </createData> - <actionGroup ref="newCatalogPriceRuleByUI" stepKey="createNewPriceRule"/> + <actionGroup stepKey="createNewPriceRule" ref="newCatalogPriceRuleByUI"/> + <actionGroup stepKey="selectLoggedInCustomers" ref="selectNotLoggedInCustomerGroup"/> <selectOption selector="{{AdminNewCatalogPriceRule.status}}" userInput="Inactive" stepKey="setInactive"/> <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApply"/> <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="seeSuccess"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/composer.json b/app/code/Magento/CatalogRule/Test/Mftf/composer.json deleted file mode 100644 index 0b57cf8dfaefa..0000000000000 --- a/app/code/Magento/CatalogRule/Test/Mftf/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-rule-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogRule/Test/Unit/Observer/AddDirtyRulesNoticeTest.php b/app/code/Magento/CatalogRule/Test/Unit/Observer/AddDirtyRulesNoticeTest.php index b052ccddbf6b4..25bae43a930bb 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Observer/AddDirtyRulesNoticeTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Observer/AddDirtyRulesNoticeTest.php @@ -49,7 +49,7 @@ public function testExecute() $eventObserverMock->expects($this->at(0))->method('getData')->with('dirty_rules')->willReturn($flagMock); $flagMock->expects($this->once())->method('getState')->willReturn(1); $eventObserverMock->expects($this->at(1))->method('getData')->with('message')->willReturn($message); - $this->messageManagerMock->expects($this->once())->method('addNotice')->with($message); + $this->messageManagerMock->expects($this->once())->method('addNoticeMessage')->with($message); $this->observer->execute($eventObserverMock); } } diff --git a/app/code/Magento/CatalogRule/etc/db_schema_whitelist.json b/app/code/Magento/CatalogRule/etc/db_schema_whitelist.json index f5aaece43f179..b6c1391ce67fb 100644 --- a/app/code/Magento/CatalogRule/etc/db_schema_whitelist.json +++ b/app/code/Magento/CatalogRule/etc/db_schema_whitelist.json @@ -1,195 +1,195 @@ { - "catalogrule": { - "column": { - "rule_id": true, - "name": true, - "description": true, - "from_date": true, - "to_date": true, - "is_active": true, - "conditions_serialized": true, - "actions_serialized": true, - "stop_rules_processing": true, - "sort_order": true, - "simple_action": true, - "discount_amount": true, - "sub_is_enable": true, - "sub_simple_action": true, - "sub_discount_amount": true - }, - "index": { - "CATALOGRULE_IS_ACTIVE_SORT_ORDER_TO_DATE_FROM_DATE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalogrule_product": { - "column": { - "rule_product_id": true, - "rule_id": true, - "from_time": true, - "to_time": true, - "customer_group_id": true, - "product_id": true, - "action_operator": true, - "action_amount": true, - "action_stop": true, - "sort_order": true, - "website_id": true, - "sub_simple_action": true, - "sub_discount_amount": true - }, - "index": { - "CATALOGRULE_PRODUCT_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_FROM_TIME": true, - "CATALOGRULE_PRODUCT_TO_TIME": true, - "CATALOGRULE_PRODUCT_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "IDX_EAA51B56FF092A0DCB795D1CEF812B7B": true, - "UNQ_EAA51B56FF092A0DCB795D1CEF812B7B": true - } - }, - "catalogrule_product_price": { - "column": { - "rule_product_price_id": true, - "rule_date": true, - "customer_group_id": true, - "product_id": true, - "rule_price": true, - "website_id": true, - "latest_start_date": true, - "earliest_end_date": true - }, - "index": { - "CATALOGRULE_PRODUCT_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_PRICE_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_PRICE_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATRULE_PRD_PRICE_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true - } - }, - "catalogrule_group_website": { - "column": { - "rule_id": true, - "customer_group_id": true, - "website_id": true - }, - "index": { - "CATALOGRULE_GROUP_WEBSITE_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATRULE_GROUP_WS_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "CATALOGRULE_GROUP_WEBSITE_RULE_ID_CATALOGRULE_RULE_ID": true, - "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "catalogrule_website": { - "column": { - "rule_id": true, - "website_id": true - }, - "index": { - "CATALOGRULE_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATALOGRULE_WEBSITE_RULE_ID_CATALOGRULE_RULE_ID": true, - "CATALOGRULE_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "catalogrule_customer_group": { - "column": { - "rule_id": true, - "customer_group_id": true - }, - "index": { - "CATALOGRULE_CUSTOMER_GROUP_CUSTOMER_GROUP_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATALOGRULE_CUSTOMER_GROUP_RULE_ID_CATALOGRULE_RULE_ID": true, - "CATRULE_CSTR_GROUP_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true - } - }, - "catalogrule_product_replica": { - "column": { - "rule_product_id": true, - "rule_id": true, - "from_time": true, - "to_time": true, - "customer_group_id": true, - "product_id": true, - "action_operator": true, - "action_amount": true, - "action_stop": true, - "sort_order": true, - "website_id": true - }, - "index": { - "CATALOGRULE_PRODUCT_REPLICA_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_REPLICA_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_REPLICA_FROM_TIME": true, - "CATALOGRULE_PRODUCT_REPLICA_TO_TIME": true, - "CATALOGRULE_PRODUCT_REPLICA_PRODUCT_ID": true, - "CATALOGRULE_PRODUCT_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_FROM_TIME": true, - "CATALOGRULE_PRODUCT_TO_TIME": true, - "CATALOGRULE_PRODUCT_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "IDX_EAA51B56FF092A0DCB795D1CEF812B7B": true, - "UNQ_EAA51B56FF092A0DCB795D1CEF812B7B": true - } - }, - "catalogrule_product_price_replica": { - "column": { - "rule_product_price_id": true, - "rule_date": true, - "customer_group_id": true, - "product_id": true, - "rule_price": true, - "website_id": true, - "latest_start_date": true, - "earliest_end_date": true - }, - "index": { - "CATALOGRULE_PRODUCT_PRICE_REPLICA_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_PRICE_REPLICA_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_PRICE_REPLICA_PRODUCT_ID": true, - "CATALOGRULE_PRODUCT_PRICE_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_PRODUCT_PRICE_WEBSITE_ID": true, - "CATALOGRULE_PRODUCT_PRICE_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CATRULE_PRD_PRICE_REPLICA_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true, - "CATRULE_PRD_PRICE_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true - } - }, - "catalogrule_group_website_replica": { - "column": { - "rule_id": true, - "customer_group_id": true, - "website_id": true - }, - "index": { - "CATALOGRULE_GROUP_WEBSITE_REPLICA_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_GROUP_WEBSITE_REPLICA_WEBSITE_ID": true, - "CATALOGRULE_GROUP_WEBSITE_CUSTOMER_GROUP_ID": true, - "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true + "catalogrule": { + "column": { + "rule_id": true, + "name": true, + "description": true, + "from_date": true, + "to_date": true, + "is_active": true, + "conditions_serialized": true, + "actions_serialized": true, + "stop_rules_processing": true, + "sort_order": true, + "simple_action": true, + "discount_amount": true, + "sub_is_enable": true, + "sub_simple_action": true, + "sub_discount_amount": true + }, + "index": { + "CATALOGRULE_IS_ACTIVE_SORT_ORDER_TO_DATE_FROM_DATE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalogrule_product": { + "column": { + "rule_product_id": true, + "rule_id": true, + "from_time": true, + "to_time": true, + "customer_group_id": true, + "product_id": true, + "action_operator": true, + "action_amount": true, + "action_stop": true, + "sort_order": true, + "website_id": true, + "sub_simple_action": true, + "sub_discount_amount": true + }, + "index": { + "CATALOGRULE_PRODUCT_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_FROM_TIME": true, + "CATALOGRULE_PRODUCT_TO_TIME": true, + "CATALOGRULE_PRODUCT_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "IDX_EAA51B56FF092A0DCB795D1CEF812B7B": true, + "UNQ_EAA51B56FF092A0DCB795D1CEF812B7B": true + } + }, + "catalogrule_product_price": { + "column": { + "rule_product_price_id": true, + "rule_date": true, + "customer_group_id": true, + "product_id": true, + "rule_price": true, + "website_id": true, + "latest_start_date": true, + "earliest_end_date": true + }, + "index": { + "CATALOGRULE_PRODUCT_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_PRICE_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_PRICE_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATRULE_PRD_PRICE_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true + } + }, + "catalogrule_group_website": { + "column": { + "rule_id": true, + "customer_group_id": true, + "website_id": true + }, + "index": { + "CATALOGRULE_GROUP_WEBSITE_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATRULE_GROUP_WS_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "CATALOGRULE_GROUP_WEBSITE_RULE_ID_CATALOGRULE_RULE_ID": true, + "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "catalogrule_website": { + "column": { + "rule_id": true, + "website_id": true + }, + "index": { + "CATALOGRULE_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATALOGRULE_WEBSITE_RULE_ID_CATALOGRULE_RULE_ID": true, + "CATALOGRULE_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "catalogrule_customer_group": { + "column": { + "rule_id": true, + "customer_group_id": true + }, + "index": { + "CATALOGRULE_CUSTOMER_GROUP_CUSTOMER_GROUP_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATALOGRULE_CUSTOMER_GROUP_RULE_ID_CATALOGRULE_RULE_ID": true, + "CATRULE_CSTR_GROUP_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true + } + }, + "catalogrule_product_replica": { + "column": { + "rule_product_id": true, + "rule_id": true, + "from_time": true, + "to_time": true, + "customer_group_id": true, + "product_id": true, + "action_operator": true, + "action_amount": true, + "action_stop": true, + "sort_order": true, + "website_id": true + }, + "index": { + "CATALOGRULE_PRODUCT_REPLICA_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_REPLICA_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_REPLICA_FROM_TIME": true, + "CATALOGRULE_PRODUCT_REPLICA_TO_TIME": true, + "CATALOGRULE_PRODUCT_REPLICA_PRODUCT_ID": true, + "CATALOGRULE_PRODUCT_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_FROM_TIME": true, + "CATALOGRULE_PRODUCT_TO_TIME": true, + "CATALOGRULE_PRODUCT_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "IDX_EAA51B56FF092A0DCB795D1CEF812B7B": true, + "UNQ_EAA51B56FF092A0DCB795D1CEF812B7B": true + } + }, + "catalogrule_product_price_replica": { + "column": { + "rule_product_price_id": true, + "rule_date": true, + "customer_group_id": true, + "product_id": true, + "rule_price": true, + "website_id": true, + "latest_start_date": true, + "earliest_end_date": true + }, + "index": { + "CATALOGRULE_PRODUCT_PRICE_REPLICA_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_PRICE_REPLICA_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_PRICE_REPLICA_PRODUCT_ID": true, + "CATALOGRULE_PRODUCT_PRICE_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_PRODUCT_PRICE_WEBSITE_ID": true, + "CATALOGRULE_PRODUCT_PRICE_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CATRULE_PRD_PRICE_REPLICA_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true, + "CATRULE_PRD_PRICE_RULE_DATE_WS_ID_CSTR_GROUP_ID_PRD_ID": true + } + }, + "catalogrule_group_website_replica": { + "column": { + "rule_id": true, + "customer_group_id": true, + "website_id": true + }, + "index": { + "CATALOGRULE_GROUP_WEBSITE_REPLICA_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_GROUP_WEBSITE_REPLICA_WEBSITE_ID": true, + "CATALOGRULE_GROUP_WEBSITE_CUSTOMER_GROUP_ID": true, + "CATALOGRULE_GROUP_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true + } } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/CatalogRule/etc/di.xml b/app/code/Magento/CatalogRule/etc/di.xml index 8ed88dd4f3fdb..e0d91db542390 100644 --- a/app/code/Magento/CatalogRule/etc/di.xml +++ b/app/code/Magento/CatalogRule/etc/di.xml @@ -126,6 +126,21 @@ </argument> </arguments> </type> + <preference for="Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface" type="Magento\CatalogRule\Model\Indexer\IndexerTableSwapper" /> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface"> + <arguments> + <argument name="priceModifiers" xsi:type="array"> + <item name="catalogRulePriceModifier" xsi:type="object">Magento\CatalogRule\Model\Indexer\ProductPriceIndexModifier</item> + </argument> + </arguments> + </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier"> + <arguments> + <argument name="priceModifiers" xsi:type="array"> + <item name="catalogRulePriceModifier" xsi:type="object">Magento\CatalogRule\Model\Indexer\ProductPriceIndexModifier</item> + </argument> + </arguments> + </type> <virtualType name="CatalogRuleCustomConditionProvider" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor\ConditionProcessor\CustomConditionProvider"> <arguments> <argument name="customConditionProcessors" xsi:type="array"> @@ -149,12 +164,4 @@ <argument name="customConditionProvider" xsi:type="object">CatalogRuleCustomConditionProvider</argument> </arguments> </type> - <preference for="Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface" type="Magento\CatalogRule\Model\Indexer\IndexerTableSwapper" /> - <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface"> - <arguments> - <argument name="priceModifiers" xsi:type="array"> - <item name="catalogRulePriceModifier" xsi:type="object">Magento\CatalogRule\Model\Indexer\ProductPriceIndexModifier</item> - </argument> - </arguments> - </type> </config> diff --git a/app/code/Magento/CatalogRule/etc/mview.xml b/app/code/Magento/CatalogRule/etc/mview.xml index 4b1166941bdc8..35efe33461afc 100644 --- a/app/code/Magento/CatalogRule/etc/mview.xml +++ b/app/code/Magento/CatalogRule/etc/mview.xml @@ -24,4 +24,9 @@ <table name="catalog_category_product" entity_column="product_id" /> </subscriptions> </view> + <view id="catalog_product_price" class="Magento\Catalog\Model\Indexer\Product\Price" group="indexer"> + <subscriptions> + <table name="catalogrule_product_price" entity_column="product_id" /> + </subscriptions> + </view> </config> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json deleted file mode 100644 index 20b1a74c9416a..0000000000000 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-rule-configurable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-catalog-rule": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogSearch/Block/Advanced/Form.php b/app/code/Magento/CatalogSearch/Block/Advanced/Form.php index 68e4233e8deaf..4d1957991d1bf 100644 --- a/app/code/Magento/CatalogSearch/Block/Advanced/Form.php +++ b/app/code/Magento/CatalogSearch/Block/Advanced/Form.php @@ -177,19 +177,11 @@ public function getCurrencyCount() * * @param AbstractAttribute $attribute * @return string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getCurrency($attribute) { return $this->_storeManager->getStore()->getCurrentCurrencyCode(); - - $baseCurrency = $this->_storeManager->getStore()->getBaseCurrency()->getCurrencyCode(); - return $this->getAttributeValue( - $attribute, - 'currency' - ) ? $this->getAttributeValue( - $attribute, - 'currency' - ) : $baseCurrency; } /** diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php index 86195aa31f0e4..d6a30f4f3141d 100644 --- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php +++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php @@ -6,7 +6,6 @@ */ namespace Magento\CatalogSearch\Controller\Advanced; -use Magento\Catalog\Model\Layer\Resolver; use Magento\CatalogSearch\Model\Advanced as ModelAdvanced; use Magento\Framework\App\Action\Context; use Magento\Framework\UrlFactory; @@ -45,7 +44,7 @@ public function __construct( } /** - * @return void + * @return \Magento\Framework\Controller\Result\Redirect */ public function execute() { @@ -58,7 +57,9 @@ public function execute() $defaultUrl = $this->_urlFactory->create() ->addQueryParams($this->getRequest()->getQueryValue()) ->getUrl('*/*/'); - $this->getResponse()->setRedirect($this->_redirect->error($defaultUrl)); + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setUrl($this->_redirect->error($defaultUrl)); + return $resultRedirect; } } } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php index ca077ef7227d5..7ebf71f424439 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilder.php @@ -8,18 +8,29 @@ use Magento\CatalogInventory\Model\Configuration as CatalogInventoryConfiguration; use Magento\CatalogInventory\Model\Stock; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\ScopeResolverInterface; -use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; use Magento\Framework\Search\Request\BucketInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; /** - * Attribute query builder + * Attribute query builder + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class QueryBuilder { + /** + * @var DimensionFactory + */ + private $dimensionFactory; + /** * @var Resource */ @@ -35,19 +46,31 @@ class QueryBuilder */ private $inventoryConfig; + /** + * @var IndexScopeResolverInterface + */ + private $priceTableResolver; + /** * @param ResourceConnection $resource * @param ScopeResolverInterface $scopeResolver * @param CatalogInventoryConfiguration $inventoryConfig + * @param IndexScopeResolverInterface $priceTableResolver + * @param DimensionFactory|null $dimensionFactory */ public function __construct( ResourceConnection $resource, ScopeResolverInterface $scopeResolver, - CatalogInventoryConfiguration $inventoryConfig + CatalogInventoryConfiguration $inventoryConfig, + IndexScopeResolverInterface $priceTableResolver = null, + DimensionFactory $dimensionFactory = null ) { $this->resource = $resource; $this->scopeResolver = $scopeResolver; $this->inventoryConfig = $inventoryConfig; + $this->priceTableResolver = $priceTableResolver + ?: ObjectManager::getInstance()->get(IndexScopeResolverInterface::class); + $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); } /** @@ -99,12 +122,25 @@ private function buildQueryForPriceAttribute( if (!$store instanceof \Magento\Store\Model\Store) { throw new \RuntimeException('Illegal scope resolved'); } + $websiteId = $store->getWebsiteId(); - $table = $this->resource->getTableName('catalog_product_index_price'); - $select->from(['main_table' => $table], null) + $tableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$websiteId + ), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$customerGroupId + ), + ] + ); + $select->from(['main_table' => $tableName], null) ->columns([BucketInterface::FIELD_VALUE => 'main_table.min_price']) ->where('main_table.customer_group_id = ?', $customerGroupId) - ->where('main_table.website_id = ?', $store->getWebsiteId()); + ->where('main_table.website_id = ?', $websiteId); return $select; } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php index 8b7a7ed214e36..d2e653ac9b9ae 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Dynamic/DataProvider.php @@ -6,17 +6,21 @@ namespace Magento\CatalogSearch\Model\Adapter\Mysql\Dynamic; use Magento\Catalog\Model\Layer\Filter\Price\Range; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Customer\Model\Session; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\DB\Select; +use Magento\Framework\Indexer\DimensionFactory; use Magento\Framework\Search\Adapter\Mysql\Aggregation\DataProviderInterface as MysqlDataProviderInterface; use Magento\Framework\Search\Dynamic\DataProviderInterface; use Magento\Framework\Search\Dynamic\IntervalFactory; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\StoreManager; +use \Magento\Framework\Search\Request\IndexScopeResolverInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -58,6 +62,16 @@ class DataProvider implements DataProviderInterface */ private $storeManager; + /** + * @var IndexScopeResolverInterface + */ + private $priceTableResolver; + + /** + * @var DimensionFactory|null + */ + private $dimensionFactory; + /** * @param ResourceConnection $resource * @param Range $range @@ -65,6 +79,8 @@ class DataProvider implements DataProviderInterface * @param MysqlDataProviderInterface $dataProvider * @param IntervalFactory $intervalFactory * @param StoreManager $storeManager + * @param IndexScopeResolverInterface|null $priceTableResolver + * @param DimensionFactory|null $dimensionFactory */ public function __construct( ResourceConnection $resource, @@ -72,7 +88,9 @@ public function __construct( Session $customerSession, MysqlDataProviderInterface $dataProvider, IntervalFactory $intervalFactory, - StoreManager $storeManager = null + StoreManager $storeManager = null, + IndexScopeResolverInterface $priceTableResolver = null, + DimensionFactory $dimensionFactory = null ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -81,6 +99,10 @@ public function __construct( $this->dataProvider = $dataProvider; $this->intervalFactory = $intervalFactory; $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManager::class); + $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get( + IndexScopeResolverInterface::class + ); + $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); } /** @@ -104,16 +126,30 @@ public function getAggregations(\Magento\Framework\Search\Dynamic\EntityStorage ]; $select = $this->getSelect(); - - $tableName = $this->resource->getTableName('catalog_product_index_price'); + $websiteId = $this->storeManager->getStore()->getWebsiteId(); + $customerGroupId = $this->customerSession->getCustomerGroupId(); + + $tableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$websiteId + ), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$customerGroupId + ), + ] + ); /** @var Table $table */ $table = $entityStorage->getSource(); $select->from(['main_table' => $tableName], []) ->where('main_table.entity_id in (select entity_id from ' . $table->getName() . ')') ->columns($aggregation); - $select = $this->setCustomerGroupId($select); - $select->where('main_table.website_id = ?', $this->storeManager->getStore()->getWebsiteId()); + $select->where('customer_group_id = ?', $customerGroupId); + $select->where('main_table.website_id = ?', $websiteId); return $this->connection->fetchRow($select); } @@ -192,13 +228,4 @@ private function getSelect() { return $this->connection->select(); } - - /** - * @param Select $select - * @return Select - */ - private function setCustomerGroupId($select) - { - return $select->where('customer_group_id = ?', $this->customerSession->getCustomerGroupId()); - } } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php index 182ecf873d77a..94432bbfe4a71 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Plugin/Aggregation/Category/DataProvider.php @@ -13,7 +13,7 @@ use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\Dimension; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; /** diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php index d51be12f01db5..37208ed896e85 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php @@ -8,19 +8,22 @@ use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\FullFactory; use Magento\CatalogSearch\Model\Indexer\Scope\StateFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext as FulltextResource; -use Magento\Framework\Indexer\Dimension\DimensionProviderInterface; +use Magento\Framework\Indexer\DimensionProviderInterface; use Magento\Store\Model\StoreDimensionProvider; +use Magento\Indexer\Model\ProcessManager; /** * Provide functionality for Fulltext Search indexing. * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * * @api * @since 100.0.2 */ class Fulltext implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface, - \Magento\Framework\Indexer\Dimension\DimensionalIndexerInterface + \Magento\Framework\Indexer\DimensionalIndexerInterface { /** * Indexer ID in configuration @@ -62,14 +65,20 @@ class Fulltext implements */ private $dimensionProvider; + /** + * @var ProcessManager + */ + private $processManager; + /** * @param FullFactory $fullActionFactory * @param IndexerHandlerFactory $indexerHandlerFactory * @param FulltextResource $fulltextResource - * @param array $data * @param IndexSwitcherInterface $indexSwitcher * @param StateFactory $indexScopeStateFactory * @param DimensionProviderInterface $dimensionProvider + * @param array $data + * @param ProcessManager $processManager */ public function __construct( FullFactory $fullActionFactory, @@ -78,7 +87,8 @@ public function __construct( IndexSwitcherInterface $indexSwitcher, StateFactory $indexScopeStateFactory, DimensionProviderInterface $dimensionProvider, - array $data + array $data, + ProcessManager $processManager = null ) { $this->fullAction = $fullActionFactory->create(['data' => $data]); $this->indexerHandlerFactory = $indexerHandlerFactory; @@ -87,6 +97,9 @@ public function __construct( $this->indexSwitcher = $indexSwitcher; $this->indexScopeState = $indexScopeStateFactory->create(); $this->dimensionProvider = $dimensionProvider; + $this->processManager = $processManager ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + ProcessManager::class + ); } /** @@ -99,7 +112,7 @@ public function __construct( public function execute($entityIds) { foreach ($this->dimensionProvider->getIterator() as $dimension) { - $this->executeByDimension($dimension, new \ArrayIterator($entityIds)); + $this->executeByDimensions($dimension, new \ArrayIterator($entityIds)); } } @@ -107,7 +120,7 @@ public function execute($entityIds) * {@inheritdoc} * @throws \InvalidArgumentException */ - public function executeByDimension(array $dimensions, \Traversable $entityIds = null) + public function executeByDimensions(array $dimensions, \Traversable $entityIds = null) { if (count($dimensions) > 1 || !isset($dimensions[StoreDimensionProvider::DIMENSION_NAME])) { throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" support only Store dimension'); @@ -145,9 +158,13 @@ public function executeByDimension(array $dimensions, \Traversable $entityIds = */ public function executeFull() { + $userFunctions = []; foreach ($this->dimensionProvider->getIterator() as $dimension) { - $this->executeByDimension($dimension); + $userFunctions[] = function () use ($dimension) { + $this->executeByDimensions($dimension); + }; } + $this->processManager->execute($userFunctions); } /** diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php index 5ad2635576857..a8d46911193a8 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php @@ -538,7 +538,7 @@ private function getProductEmulator($typeId) * @param array $indexData * @param array $productData * @param int $storeId - * @return string + * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @since 100.0.3 */ diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/EngineInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/EngineInterface.php index 399f8f763d945..0182b09bcacff 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/EngineInterface.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/EngineInterface.php @@ -61,7 +61,7 @@ public function processAttributeValue($attribute, $value); * * @param array $index * @param string $separator - * @return string + * @return array */ public function prepareEntityIndex($index, $separator = ' '); } diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php index 66e0457e7fadd..512dd69aad952 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php @@ -7,13 +7,21 @@ namespace Magento\CatalogSearch\Model\Search\FilterMapper; use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Framework\App\Http\Context; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver as TableResolver; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; use Magento\Framework\Search\Request\Dimension; use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction; +use Magento\Customer\Model\Context as CustomerContext; +use Magento\Framework\Search\Request\IndexScopeResolverInterface; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; /** * Strategy which processes exclusions from general rules + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ExclusionStrategy implements FilterStrategyInterface { @@ -43,22 +51,48 @@ class ExclusionStrategy implements FilterStrategyInterface */ private $tableResolver; + /** + * @var IndexScopeResolverInterface + */ + private $priceTableResolver; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @var Context + */ + private $httpContext; + /** * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param AliasResolver $aliasResolver * @param TableResolver|null $tableResolver + * @param DimensionFactory $dimensionFactory + * @param IndexScopeResolverInterface $priceTableResolver + * @param Context $httpContext */ public function __construct( \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Store\Model\StoreManagerInterface $storeManager, AliasResolver $aliasResolver, - TableResolver $tableResolver = null + TableResolver $tableResolver = null, + DimensionFactory $dimensionFactory = null, + IndexScopeResolverInterface $priceTableResolver = null, + Context $httpContext = null ) { $this->resourceConnection = $resourceConnection; $this->storeManager = $storeManager; $this->aliasResolver = $aliasResolver; $this->tableResolver = $tableResolver ?: ObjectManager::getInstance()->get(TableResolver::class); + $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); + $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get( + IndexScopeResolverInterface::class + ); + $this->httpContext = $httpContext ?: ObjectManager::getInstance()->get(Context::class); } /** @@ -93,7 +127,17 @@ private function applyPriceFilter( \Magento\Framework\DB\Select $select ) { $alias = $this->aliasResolver->getAlias($filter); - $tableName = $this->resourceConnection->getTableName('catalog_product_index_price'); + $websiteId = $this->storeManager->getWebsite()->getId(); + $tableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create(WebsiteDimensionProvider::DIMENSION_NAME, (string)$websiteId), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$this->httpContext->getValue(CustomerContext::CONTEXT_GROUP) + ) + ] + ); $mainTableAlias = $this->extractTableAliasFromSelect($select); $select->joinInner( @@ -102,7 +146,7 @@ private function applyPriceFilter( ], $this->resourceConnection->getConnection()->quoteInto( sprintf('%s.entity_id = price_index.entity_id AND price_index.website_id = ?', $mainTableAlias), - $this->storeManager->getWebsite()->getId() + $websiteId ), [] ); diff --git a/app/code/Magento/CatalogSearch/Plugin/EnableEavIndexer.php b/app/code/Magento/CatalogSearch/Plugin/EnableEavIndexer.php new file mode 100644 index 0000000000000..c624f9d1c2e52 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Plugin/EnableEavIndexer.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Plugin; + +/** + * Enable Product EAV indexer in configuration for MySQL search engine + */ +class EnableEavIndexer +{ + /** + * Config search engine path + */ + const SEARCH_ENGINE_VALUE_PATH = 'groups/search/fields/engine/value'; + + /** + * @param \Magento\Config\Model\Config $subject + */ + public function beforeSave(\Magento\Config\Model\Config $subject) + { + $searchEngine = $subject->getData(self::SEARCH_ENGINE_VALUE_PATH); + if ($searchEngine === 'mysql') { + $data = $subject->getData(); + $data['groups']['search']['fields']['enable_eav_indexer']['value'] = 1; + + $subject->setData($data); + } + } +} diff --git a/app/code/Magento/CatalogSearch/Setup/Patch/Data/SetInitialSearchWeightForAttributes.php b/app/code/Magento/CatalogSearch/Setup/Patch/Data/SetInitialSearchWeightForAttributes.php index e266e67804e88..beff1c66d4f61 100644 --- a/app/code/Magento/CatalogSearch/Setup/Patch/Data/SetInitialSearchWeightForAttributes.php +++ b/app/code/Magento/CatalogSearch/Setup/Patch/Data/SetInitialSearchWeightForAttributes.php @@ -6,6 +6,7 @@ namespace Magento\CatalogSearch\Setup\Patch\Data; +use Magento\Framework\App\State; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; use Magento\Framework\Indexer\IndexerInterfaceFactory; @@ -27,17 +28,25 @@ class SetInitialSearchWeightForAttributes implements DataPatchInterface, PatchVe */ private $attributeRepository; + /** + * @var State + */ + private $state; + /** * SetInitialSearchWeightForAttributes constructor. * @param IndexerInterfaceFactory $indexerFactory * @param ProductAttributeRepositoryInterface $attributeRepository + * @param State $state */ public function __construct( IndexerInterfaceFactory $indexerFactory, - ProductAttributeRepositoryInterface $attributeRepository + ProductAttributeRepositoryInterface $attributeRepository, + State $state ) { $this->indexerFactory = $indexerFactory; $this->attributeRepository = $attributeRepository; + $this->state = $state; } /** @@ -47,6 +56,13 @@ public function apply() { $this->setWeight('sku', 6); $this->setWeight('name', 5); + $indexer = $this->indexerFactory->create()->load('catalogsearch_fulltext'); + $this->state->emulateAreaCode( + \Magento\Framework\App\Area::AREA_CRONTAB, + function () use ($indexer) { + $indexer->reindexAll(); + } + ); } /** @@ -76,8 +92,9 @@ public function getAliases() /** * Set attribute search weight. * - * @param $attributeCode - * @param $weight + * @param string $attributeCode + * @param int $weight + * @return void */ private function setWeight($attributeCode, $weight) { diff --git a/app/code/Magento/CatalogSearch/Setup/RecurringData.php b/app/code/Magento/CatalogSearch/Setup/RecurringData.php deleted file mode 100644 index 0c2aee800b6f1..0000000000000 --- a/app/code/Magento/CatalogSearch/Setup/RecurringData.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\CatalogSearch\Setup; - -use Magento\Framework\App\State; -use Magento\Framework\Indexer\IndexerInterfaceFactory; -use Magento\Framework\Setup\InstallDataInterface; -use Magento\Framework\Setup\ModuleContextInterface; -use Magento\Framework\Setup\ModuleDataSetupInterface; - -/** - * Recurring data install. - */ -class RecurringData implements InstallDataInterface -{ - /** - * @var IndexerInterfaceFactory - */ - private $indexerInterfaceFactory; - /** - * @var State - */ - private $state; - - /** - * Init - * - * @param IndexerInterfaceFactory $indexerInterfaceFactory - */ - public function __construct( - IndexerInterfaceFactory $indexerInterfaceFactory, - State $state - ) { - $this->indexerInterfaceFactory = $indexerInterfaceFactory; - $this->state = $state; - } - - /** - * {@inheritdoc} - */ - public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) - { - $this->state->emulateAreaCode( - \Magento\Framework\App\Area::AREA_CRONTAB, - [$this, 'reindex'] - ); - } - - /** - * Run reindex. - * - * @return void - */ - public function reindex() - { - $this->indexerInterfaceFactory->create()->load('catalogsearch_fulltext')->reindexAll(); - } -} diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml index c4bb5ff4b6dc7..51dd8a80fcb41 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Quick search the phrase and check if the result page contains correct information --> <actionGroup name="StorefrontCheckQuickSearchActionGroup"> <arguments> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/ConstData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/ConstData.xml index 08fc1ce00e5e4..52fd61301c3b3 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/ConstData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="CONST" type="CONST"> <data key="apiSimpleProduct">Api Simple Product</data> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedFormPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedFormPage.xml index c52d816f0f30a..28515c8186a23 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedFormPage.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedFormPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCatalogSearchAdvancedFormPage" url="/catalogsearch/advanced/" area="storefront" module="Magento_CatalogSearch"> <section name="StorefrontCatalogSearchAdvancedFormSection" /> <section name="StorefrontQuickSearchSection" /> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedResultPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedResultPage.xml index 422ccc652b793..0584f5e338035 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedResultPage.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchAdvancedResultPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCatalogSearchAdvancedResultPage" url="/catalogsearch/advanced/result" area="storefront" module="Magento_CatalogSearch"> <section name="StorefrontCatalogSearchAdvancedResultMainSection" /> <section name="StorefrontQuickSearchSection" /> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchPage.xml index 6141aa96226c0..0700adb6d30e0 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchPage.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/StorefrontCatalogSearchPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCatalogSearchPage" url="/catalogsearch/result/" area="storefront" module="Magento_CatalogSearch"> <section name="StorefrontCatalogSearchMainSection" /> <section name="StorefrontQuickSearchSection" /> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml index d7c63ca1af2de..6889025530098 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedFormSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCatalogSearchAdvancedFormSection"> <element name="SearchTitle" type="text" selector=".page-title span"/> <element name="ProductName" type="input" selector="#name"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml index d0634754eeed8..6b28b4f36c6a7 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchAdvancedResultMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCatalogSearchAdvancedResultMainSection"> <element name="SearchTitle" type="text" selector=".page-title span"/> <element name="ProductItemInfo" type="button" selector=".product-item-info"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml index 165629f1cea81..8b35ef3336175 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCatalogSearchMainSection"> <element name="SearchTitle" type="text" selector=".page-title span"/> <element name="ProductItemInfo" type="button" selector=".product-item-info"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontFooterSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontFooterSection.xml index dae21aeefc1f9..dbecf55a0104b 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontFooterSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontFooterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontFooterSection"> <element name="AdvancedSearch" type="button" selector="//footer//ul//li//a[text()='Advanced Search']"/> </section> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml index f33f3db14b6cc..13665100f79af 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="CatalogSearch"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index b19c00eaf325b..99f3fc00a7401 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <!-- Step 2: User searches for product --> <comment userInput="Start of searching products" stepKey="startOfSearchingProducts" after="endOfBrowsingCatalog"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 5669a788105fa..367cb6a6e214e 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <!-- Step 2: User searches for product --> <comment userInput="Start of searching products" stepKey="startOfSearchingProducts" after="endOfBrowsingCatalog"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/composer.json b/app/code/Magento/CatalogSearch/Test/Mftf/composer.json deleted file mode 100644 index 7d54620eeac83..0000000000000 --- a/app/code/Magento/CatalogSearch/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-search", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-search": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php index 9aa97f8e6b52a..891f008979e17 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php @@ -5,6 +5,9 @@ */ namespace Magento\CatalogSearch\Test\Unit\Controller\Advanced; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ResultTest extends \PHPUnit\Framework\TestCase { public function testResultActionFiltersSetBeforeLoadLayout() @@ -49,4 +52,90 @@ function ($added) use (&$filters) { ); $instance->execute(); } + + public function testUrlSetOnException() + { + $redirectResultMock = $this->createMock(\Magento\Framework\Controller\Result\Redirect::class); + $redirectResultMock->expects($this->once()) + ->method('setUrl'); + + $redirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + + $redirectFactoryMock->expects($this->any()) + ->method('create') + ->willReturn($redirectResultMock); + + $catalogSearchAdvanced = $this->createPartialMock( + \Magento\CatalogSearch\Model\Advanced::class, + ['addFilters'] + ); + + $catalogSearchAdvanced->expects($this->once())->method('addFilters')->will( + $this->throwException(new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase("Test Exception") + )) + ); + + $responseMock = $this->createMock(\Magento\Framework\Webapi\Response::class); + $requestMock = $this->createPartialMock( + \Magento\Framework\App\Request\Http::class, + ['getQueryValue'] + ); + $requestMock->expects($this->any())->method('getQueryValue')->willReturn(['key' => 'value']); + + $redirectMock = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class); + $redirectMock->expects($this->any())->method('error')->with('urlstring'); + + $messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); + + $eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + + $contextMock = $this->createMock(\Magento\Framework\App\Action\Context::class); + $contextMock->expects($this->any()) + ->method('getRequest') + ->willReturn($requestMock); + $contextMock->expects($this->any()) + ->method('getResponse') + ->willReturn($responseMock); + $contextMock->expects($this->any()) + ->method('getRedirect') + ->willReturn($redirectMock); + $contextMock->expects($this->any()) + ->method('getMessageManager') + ->willReturn($messageManagerMock); + $contextMock->expects($this->any()) + ->method('getEventManager') + ->willReturn($eventManagerMock); + $contextMock->expects($this->any()) + ->method('getResultRedirectFactory') + ->willReturn($redirectFactoryMock); + + $urlMock = $this->createMock(\Magento\Framework\Url::class); + $urlMock->expects($this->once()) + ->method('addQueryParams') + ->willReturnSelf(); + $urlMock->expects($this->once()) + ->method('getUrl') + ->willReturn("urlstring"); + + $urlFactoryMock = $this->createMock(\Magento\Framework\UrlFactory::class); + $urlFactoryMock->expects($this->once()) + ->method('create') + ->will($this->returnValue($urlMock)); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + /** @var \Magento\CatalogSearch\Controller\Advanced\Result $instance */ + $instance = $objectManager->getObject( + \Magento\CatalogSearch\Controller\Advanced\Result::class, + ['context' => $contextMock, + 'catalogSearchAdvanced' => $catalogSearchAdvanced, + 'urlFactory' => $urlFactoryMock + ] + ); + $this->assertEquals($redirectResultMock, $instance->execute()); + } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php index b52664df749fe..72379c3819dea 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProvider/QueryBuilderTest.php @@ -17,7 +17,9 @@ use Magento\Store\Model\Store; /** - * Test for Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder. + * Test for Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class QueryBuilderTest extends \PHPUnit\Framework\TestCase { @@ -57,10 +59,25 @@ protected function setUp() ->method('getConnection') ->willReturn($this->adapterMock); + $this->indexScopeResolverMock = $this->createMock( + \Magento\Framework\Search\Request\IndexScopeResolverInterface::class + ); + $this->dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + $this->dimensionFactoryMock = $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class); + $this->dimensionFactoryMock->method('create')->willReturn($this->dimensionMock); + $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $storeMock->method('getId')->willReturn(1); + $storeMock->method('getWebsiteId')->willReturn(1); + $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + $this->indexScopeResolverMock->method('resolve')->willReturn('catalog_product_index_price'); + $this->model = new QueryBuilder( $this->resourceConnectionMock, $this->scopeResolverMock, - $this->inventoryConfigMock + $this->inventoryConfigMock, + $this->indexScopeResolverMock, + $this->dimensionFactoryMock ); } @@ -81,8 +98,6 @@ public function testBuildWithPriceAttributeCode() $this->scopeResolverMock->expects($this->once())->method('getScope') ->with($scope)->willReturn($storeMock); $storeMock->expects($this->once())->method('getWebsiteId')->willReturn(1); - $this->resourceConnectionMock->expects($this->once())->method('getTableName') - ->with('catalog_product_index_price')->willReturn('catalog_product_index_price'); $selectMock->expects($this->once())->method('from') ->with(['main_table' => 'catalog_product_index_price'], null) ->willReturn($selectMock); diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Dynamic/DataProviderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Dynamic/DataProviderTest.php index 1aeeb0d9bd731..1186dd6936cc6 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Dynamic/DataProviderTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Dynamic/DataProviderTest.php @@ -74,6 +74,18 @@ protected function setUp() $this->mysqlDataProviderMock = $this->createMock(DataProviderInterface::class); $this->intervalFactoryMock = $this->createMock(IntervalFactory::class); $this->storeManagerMock = $this->createMock(StoreManager::class); + $this->indexScopeResolverMock = $this->createMock( + \Magento\Framework\Search\Request\IndexScopeResolverInterface::class + ); + $this->dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + $this->dimensionFactoryMock = $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class); + $this->dimensionFactoryMock->method('create')->willReturn($this->dimensionMock); + $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $storeMock->method('getId')->willReturn(1); + $storeMock->method('getWebsiteId')->willReturn(1); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + $this->indexScopeResolverMock->method('resolve')->willReturn('catalog_product_index_price'); + $this->sessionMock->method('getCustomerGroupId')->willReturn(1); $this->model = new DataProvider( $this->resourceConnectionMock, @@ -81,7 +93,9 @@ protected function setUp() $this->sessionMock, $this->mysqlDataProviderMock, $this->intervalFactoryMock, - $this->storeManagerMock + $this->storeManagerMock, + $this->indexScopeResolverMock, + $this->dimensionFactoryMock ); } @@ -97,10 +111,6 @@ public function testGetAggregationsUsesFrontendPriceIndexerTable() $entityStorageMock = $this->createMock(EntityStorage::class); $entityStorageMock->expects($this->any())->method('getSource')->willReturn($tableMock); - $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); - $storeMock->expects($this->once())->method('getWebsiteId')->willReturn(42); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); - $this->model->getAggregations($entityStorageMock); } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/FulltextTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/FulltextTest.php index 0c3bd42a1b5eb..d7129b9c224fc 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/FulltextTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/FulltextTest.php @@ -6,7 +6,7 @@ namespace Magento\CatalogSearch\Test\Unit\Model\Indexer; use \Magento\Framework\Indexer\Dimension; -use Magento\Framework\Indexer\Dimension\DimensionProviderInterface; +use Magento\Framework\Indexer\DimensionProviderInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** @@ -44,6 +44,11 @@ class FulltextTest extends \PHPUnit\Framework\TestCase */ private $dimensionProviderMock; + /** + * @var \Magento\Indexer\Model\ProcessManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $processManager; + protected function setUp() { $this->fullAction = $this->getClassMock(\Magento\CatalogSearch\Model\Indexer\Fulltext\Action\Full::class); @@ -70,6 +75,11 @@ protected function setUp() $stateMock = $this->getMockBuilder(\Magento\CatalogSearch\Model\Indexer\Scope\State::class) ->getMock(); $objectManagerHelper = new ObjectManagerHelper($this); + + $this->processManager = new \Magento\Indexer\Model\ProcessManager( + $this->getClassMock(\Magento\Framework\App\ResourceConnection::class) + ); + $this->model = $objectManagerHelper->getObject( \Magento\CatalogSearch\Model\Indexer\Fulltext::class, [ @@ -80,6 +90,7 @@ protected function setUp() 'indexSwitcher' => $this->indexSwitcher, 'dimensionProvider' => $this->dimensionProviderMock, 'indexScopeState' => $stateMock, + 'processManager' => $this->processManager, ] ); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/ExclusionStrategyTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/ExclusionStrategyTest.php index 7c6cafd7e9924..09591532f9f06 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/ExclusionStrategyTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/ExclusionStrategyTest.php @@ -15,6 +15,9 @@ use Magento\Framework\Search\Request\Filter\Term; use Magento\Store\Api\Data\WebsiteInterface; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ExclusionStrategyTest extends \PHPUnit\Framework\TestCase { /** @@ -50,10 +53,31 @@ protected function setUp() $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->aliasResolverMock = $this->createMock(AliasResolver::class); + $this->indexScopeResolverMock = $this->createMock( + \Magento\Framework\Search\Request\IndexScopeResolverInterface::class + ); + $this->tableResolverMock = $this->createMock( + \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver::class + ); + $this->dimensionMock = $this->createMock(\Magento\Framework\Indexer\Dimension::class); + $this->dimensionFactoryMock = $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class); + $this->dimensionFactoryMock->method('create')->willReturn($this->dimensionMock); + $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); + $storeMock->method('getId')->willReturn(1); + $storeMock->method('getWebsiteId')->willReturn(1); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); + $this->indexScopeResolverMock->method('resolve')->willReturn('catalog_product_index_price'); + $this->httpContextMock = $this->createMock(\Magento\Framework\App\Http\Context::class); + $this->httpContextMock->method('getValue')->willReturn(1); + $this->model = new ExclusionStrategy( $this->resourceConnectionMock, $this->storeManagerMock, - $this->aliasResolverMock + $this->aliasResolverMock, + $this->tableResolverMock, + $this->dimensionFactoryMock, + $this->indexScopeResolverMock, + $this->httpContextMock ); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Plugin/EnableEavIndexerTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Plugin/EnableEavIndexerTest.php new file mode 100644 index 0000000000000..0eac2e3309aec --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Plugin/EnableEavIndexerTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Test\Unit\Plugin; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class EnableEavIndexerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\CatalogSearch\Plugin\EnableEavIndexer + */ + private $model; + + /** + * @var \Magento\Config\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $config; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $this->config = $this->getMockBuilder(\Magento\Config\Model\Config::class) + ->disableOriginalConstructor() + ->setMethods(['getData', 'setData']) + ->getMock(); + + $objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $objectManagerHelper->getObject( + \Magento\CatalogSearch\Plugin\EnableEavIndexer::class + ); + } + + /** + * Test with other search engine (not MySQL) selected in config + * + * @return void + */ + public function testBeforeSave() + { + $this->config->expects($this->once())->method('getData')->willReturn('elasticsearch'); + $this->config->expects($this->never())->method('setData')->willReturnSelf(); + + $this->model->beforeSave($this->config); + } + + /** + * Test with MySQL search engine selected in config + * + * @return void + */ + public function testBeforeSaveMysqlSearchEngine() + { + $this->config->expects($this->at(0))->method('getData')->willReturn('mysql'); + $this->config->expects($this->at(1))->method('getData')->willReturn([]); + $this->config->expects($this->once())->method('setData')->willReturnSelf(); + + $this->model->beforeSave($this->config); + } +} diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 72bf2ec90a582..1000e349b6f9a 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -9,6 +9,7 @@ "magento/framework": "*", "magento/module-backend": "*", "magento/module-catalog": "*", + "magento/module-indexer": "*", "magento/module-catalog-inventory": "*", "magento/module-customer": "*", "magento/module-directory": "*", @@ -18,6 +19,9 @@ "magento/module-theme": "*", "magento/module-ui": "*" }, + "suggest": { + "magento/module-config": "*" + }, "type": "magento2-module", "license": [ "OSL-3.0", diff --git a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml index 18d2cdf542799..b8f2863139e9b 100644 --- a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml @@ -27,7 +27,7 @@ <label>Maximum Query Length</label> <validate>validate-digits</validate> </field> - <field id="max_count_cacheable_search_terms" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="max_count_cacheable_search_terms" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Number of top search results to cache</label> <comment>Number of popular search terms to be cached for faster response. Use “0” to cache all results after a term is searched for the second time.</comment> <validate>validate-digits</validate> @@ -36,6 +36,14 @@ <label>Autocomplete Limit</label> <validate>validate-digits</validate> </field> + <field id="enable_eav_indexer" translate="label" type="select" sortOrder="18" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Enable EAV Indexer</label> + <comment>Enable/Disable Product EAV indexer to improve indexation speed. Make sure that indexer is not used by 3rd party extensions.</comment> + <depends> + <field id="engine" separator="," negative="1">mysql</field> + </depends> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> </group> </section> </system> diff --git a/app/code/Magento/CatalogSearch/etc/config.xml b/app/code/Magento/CatalogSearch/etc/config.xml index d2b50fe9f5336..66b79226c9f34 100644 --- a/app/code/Magento/CatalogSearch/etc/config.xml +++ b/app/code/Magento/CatalogSearch/etc/config.xml @@ -17,6 +17,7 @@ <max_query_length>128</max_query_length> <max_count_cacheable_search_terms>100</max_count_cacheable_search_terms> <autocomplete_limit>8</autocomplete_limit> + <enable_eav_indexer>1</enable_eav_indexer> </search> </catalog> </default> diff --git a/app/code/Magento/CatalogSearch/etc/db_schema_whitelist.json b/app/code/Magento/CatalogSearch/etc/db_schema_whitelist.json index 453c0c9c90743..0d94c3a63a850 100644 --- a/app/code/Magento/CatalogSearch/etc/db_schema_whitelist.json +++ b/app/code/Magento/CatalogSearch/etc/db_schema_whitelist.json @@ -1,7 +1,7 @@ { - "catalog_eav_attribute": { - "column": { - "search_weight": true + "catalog_eav_attribute": { + "column": { + "search_weight": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index 3d1c4470b1ae8..cc07384d4c525 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -316,4 +316,28 @@ </argument> </arguments> </type> + <type name="Magento\CatalogSearch\Model\Search\FilterMapper\ExclusionStrategy"> + <arguments> + <argument name="priceTableResolver" xsi:type="object"> + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + </argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Model\Adapter\Mysql\Dynamic\DataProvider"> + <arguments> + <argument name="priceTableResolver" xsi:type="object"> + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + </argument> + </arguments> + </type> + <type name="Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation\DataProvider\QueryBuilder"> + <arguments> + <argument name="priceTableResolver" xsi:type="object"> + Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + </argument> + </arguments> + </type> + <type name="Magento\Config\Model\Config"> + <plugin name="config_enable_eav_indexer" type="Magento\CatalogSearch\Plugin\EnableEavIndexer" /> + </type> </config> diff --git a/app/code/Magento/CatalogSearch/etc/module.xml b/app/code/Magento/CatalogSearch/etc/module.xml index 68253014ec150..8fe282ce7539b 100644 --- a/app/code/Magento/CatalogSearch/etc/module.xml +++ b/app/code/Magento/CatalogSearch/etc/module.xml @@ -10,6 +10,7 @@ <sequence> <module name="Magento_Search"/> <module name="Magento_Catalog"/> + <module name="Magento_Indexer"/> <module name="Magento_CatalogRule" /> <module name="Magento_CatalogInventory" /> </sequence> diff --git a/app/code/Magento/CatalogSearch/i18n/en_US.csv b/app/code/Magento/CatalogSearch/i18n/en_US.csv index 9121520774ccc..ba97dc9de1d31 100644 --- a/app/code/Magento/CatalogSearch/i18n/en_US.csv +++ b/app/code/Magento/CatalogSearch/i18n/en_US.csv @@ -37,3 +37,4 @@ name,name "Minimal Query Length","Minimal Query Length" "Maximum Query Length","Maximum Query Length" "Rebuild Catalog product fulltext search index","Rebuild Catalog product fulltext search index" +"Please enter a valid price range.","Please enter a valid price range." diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml index 53a301022873b..95ea7fcef3a1a 100644 --- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml +++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/form.phtml @@ -147,8 +147,8 @@ require([ } }, messages: { - 'price[to]': {'greater-than-equals-to': 'Please enter a valid price range.'}, - 'price[from]': {'less-than-equals-to': 'Please enter a valid price range.'} + 'price[to]': {'greater-than-equals-to': '<?= /* @escapeNotVerified */ __('Please enter a valid price range.') ?>'}, + 'price[from]': {'less-than-equals-to': '<?= /* @escapeNotVerified */ __('Please enter a valid price range.') ?>'} } }); }); diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index 2e192d895c6d5..4fdb9a3e2138d 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -5,8 +5,6 @@ */ namespace Magento\CatalogUrlRewrite\Model; -use Magento\Store\Model\Store; - class ProductUrlPathGenerator { const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; @@ -120,11 +118,12 @@ public function getCanonicalUrlPath($product, $category = null) * Generate product url key based on url_key entered by merchant or product name * * @param \Magento\Catalog\Model\Product $product - * @return string + * @return string|null */ public function getUrlKey($product) { - return $product->getUrlKey() === false ? false : $this->prepareProductUrlKey($product); + $generatedProductUrlKey = $this->prepareProductUrlKey($product); + return ($product->getUrlKey() === false || empty($generatedProductUrlKey)) ? null : $generatedProductUrlKey; } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php index b201ae31b680a..28afff56c019f 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductUrlKeyAutogeneratorObserver.php @@ -33,6 +33,9 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var Product $product */ $product = $observer->getEvent()->getProduct(); - $product->setUrlKey($this->productUrlPathGenerator->getUrlKey($product)); + $urlKey = $this->productUrlPathGenerator->getUrlKey($product); + if (null !== $urlKey) { + $product->setUrlKey($urlKey); + } } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index 9892465d1538a..c4ec0bb3a74b2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -3,27 +3,48 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Observer; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider; +use Magento\CatalogUrlRewrite\Model\CategoryProductUrlPathGenerator; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\UrlRewrite\Model\MergeDataProvider; +use Magento\UrlRewrite\Model\MergeDataProviderFactory; +use Magento\UrlRewrite\Model\UrlPersistInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class UrlRewriteHandler { /** - * @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider + * @var ChildrenCategoriesProvider */ protected $childrenCategoriesProvider; /** - * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator + * @var CategoryUrlRewriteGenerator */ protected $categoryUrlRewriteGenerator; /** - * @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator + * @var ProductUrlRewriteGenerator */ protected $productUrlRewriteGenerator; /** - * @var \Magento\UrlRewrite\Model\UrlPersistInterface + * @var UrlPersistInterface */ protected $urlPersist; @@ -33,44 +54,51 @@ class UrlRewriteHandler protected $isSkippedProduct; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory + * @var CollectionFactory */ protected $productCollectionFactory; /** - * @var \Magento\CatalogUrlRewrite\Model\CategoryProductUrlPathGenerator + * @var CategoryProductUrlPathGenerator */ private $categoryBasedProductRewriteGenerator; /** - * @var \Magento\UrlRewrite\Model\MergeDataProvider + * @var MergeDataProvider */ private $mergeDataProviderPrototype; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var Json */ private $serializer; /** - * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider - * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator - * @param \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator - * @param \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist - * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory - * @param \Magento\CatalogUrlRewrite\Model\CategoryProductUrlPathGenerator $categoryBasedProductRewriteGenerator - * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + * @var ProductScopeRewriteGenerator + */ + private $productScopeRewriteGenerator; + + /** + * @param ChildrenCategoriesProvider $childrenCategoriesProvider + * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator + * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator + * @param UrlPersistInterface $urlPersist + * @param CollectionFactory $productCollectionFactory + * @param CategoryProductUrlPathGenerator $categoryBasedProductRewriteGenerator + * @param MergeDataProviderFactory|null $mergeDataProviderFactory + * @param Json|null $serializer + * @param ProductScopeRewriteGenerator|null $productScopeRewriteGenerator */ public function __construct( - \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider, - \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator, - \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator $productUrlRewriteGenerator, - \Magento\UrlRewrite\Model\UrlPersistInterface $urlPersist, - \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, - \Magento\CatalogUrlRewrite\Model\CategoryProductUrlPathGenerator $categoryBasedProductRewriteGenerator, - \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null, - \Magento\Framework\Serialize\Serializer\Json $serializer = null + ChildrenCategoriesProvider $childrenCategoriesProvider, + CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator, + ProductUrlRewriteGenerator $productUrlRewriteGenerator, + UrlPersistInterface $urlPersist, + CollectionFactory $productCollectionFactory, + CategoryProductUrlPathGenerator $categoryBasedProductRewriteGenerator, + MergeDataProviderFactory $mergeDataProviderFactory = null, + Json $serializer = null, + ProductScopeRewriteGenerator $productScopeRewriteGenerator = null ) { $this->childrenCategoriesProvider = $childrenCategoriesProvider; $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator; @@ -79,58 +107,29 @@ public function __construct( $this->productCollectionFactory = $productCollectionFactory; $this->categoryBasedProductRewriteGenerator = $categoryBasedProductRewriteGenerator; - if (!isset($mergeDataProviderFactory)) { - $mergeDataProviderFactory = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\UrlRewrite\Model\MergeDataProviderFactory::class - ); - } - + $objectManager = ObjectManager::getInstance(); + $mergeDataProviderFactory = $mergeDataProviderFactory ?: $objectManager->get(MergeDataProviderFactory::class); $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); - - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Framework\Serialize\Serializer\Json::class - ); + $this->serializer = $serializer ?: $objectManager->get(Json::class); + $this->productScopeRewriteGenerator = $productScopeRewriteGenerator + ?: $objectManager->get(ProductScopeRewriteGenerator::class); } /** - * Generate url rewrites for products assigned to category + * Generates URL rewrites for products assigned to category. * - * @param \Magento\Catalog\Model\Category $category + * @param Category $category * @return array */ - public function generateProductUrlRewrites(\Magento\Catalog\Model\Category $category) + public function generateProductUrlRewrites(Category $category): array { $mergeDataProvider = clone $this->mergeDataProviderPrototype; $this->isSkippedProduct[$category->getEntityId()] = []; - $saveRewriteHistory = $category->getData('save_rewrites_history'); - $storeId = $category->getStoreId(); + $saveRewriteHistory = (bool)$category->getData('save_rewrites_history'); + $storeId = (int)$category->getStoreId(); if ($category->getChangedProductIds()) { - $this->isSkippedProduct[$category->getEntityId()] = $category->getAffectedProductIds(); - /* @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ - $collection = $this->productCollectionFactory->create() - ->setStoreId($storeId) - ->addIdFilter($category->getAffectedProductIds()) - ->addAttributeToSelect('visibility') - ->addAttributeToSelect('name') - ->addAttributeToSelect('url_key') - ->addAttributeToSelect('url_path'); - - $collection->setPageSize(1000); - $pageCount = $collection->getLastPageNumber(); - $currentPage = 1; - while ($currentPage <= $pageCount) { - $collection->setCurPage($currentPage); - foreach ($collection as $product) { - $product->setStoreId($storeId); - $product->setData('save_rewrites_history', $saveRewriteHistory); - $mergeDataProvider->merge( - $this->productUrlRewriteGenerator->generate($product, $category->getEntityId()) - ); - } - $collection->clear(); - $currentPage++; - } + $this->generateChangedProductUrls($mergeDataProvider, $category, $storeId, $saveRewriteHistory); } else { $mergeDataProvider->merge( $this->getCategoryProductsUrlRewrites( @@ -157,21 +156,49 @@ public function generateProductUrlRewrites(\Magento\Catalog\Model\Category $cate } /** - * @param \Magento\Catalog\Model\Category $category + * @param Category $category + * @return void + */ + public function deleteCategoryRewritesForChildren(Category $category) + { + $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true); + $categoryIds[] = $category->getId(); + foreach ($categoryIds as $categoryId) { + $this->urlPersist->deleteByData( + [ + UrlRewrite::ENTITY_ID => + $categoryId, + UrlRewrite::ENTITY_TYPE => + CategoryUrlRewriteGenerator::ENTITY_TYPE, + ] + ); + $this->urlPersist->deleteByData( + [ + UrlRewrite::METADATA => + $this->serializer->serialize(['category_id' => $categoryId]), + UrlRewrite::ENTITY_TYPE => + ProductUrlRewriteGenerator::ENTITY_TYPE, + ] + ); + } + } + + /** + * @param Category $category * @param int $storeId * @param bool $saveRewriteHistory * @param int|null $rootCategoryId * @return array */ private function getCategoryProductsUrlRewrites( - \Magento\Catalog\Model\Category $category, + Category $category, $storeId, $saveRewriteHistory, $rootCategoryId = null ) { $mergeDataProvider = clone $this->mergeDataProviderPrototype; - /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ + /** @var Collection $productCollection */ $productCollection = $this->productCollectionFactory->create(); $productCollection->addCategoriesFilter(['eq' => [$category->getEntityId()]]) @@ -199,30 +226,65 @@ private function getCategoryProductsUrlRewrites( } /** - * @param \Magento\Catalog\Model\Category $category + * Generates product URL rewrites. + * + * @param MergeDataProvider $mergeDataProvider + * @param Category $category + * @param int $storeId + * @param bool $saveRewriteHistory * @return void */ - public function deleteCategoryRewritesForChildren(\Magento\Catalog\Model\Category $category) - { - $categoryIds = $this->childrenCategoriesProvider->getChildrenIds($category, true); - $categoryIds[] = $category->getId(); - foreach ($categoryIds as $categoryId) { - $this->urlPersist->deleteByData( - [ - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_ID => - $categoryId, - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE => - \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::ENTITY_TYPE, - ] - ); - $this->urlPersist->deleteByData( - [ - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::METADATA => - $this->serializer->serialize(['category_id' => $categoryId]), - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::ENTITY_TYPE => - \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE, - ] - ); + private function generateChangedProductUrls( + MergeDataProvider $mergeDataProvider, + Category $category, + int $storeId, + bool $saveRewriteHistory + ) { + $this->isSkippedProduct[$category->getEntityId()] = $category->getAffectedProductIds(); + + $categoryStoreIds = [$storeId]; + // If category is changed in the Global scope when need to regenerate product URL rewrites for all other scopes. + if ($this->productScopeRewriteGenerator->isGlobalScope($storeId)) { + $categoryStoreIds = $this->getCategoryStoreIds($category); + } + + foreach ($categoryStoreIds as $categoryStoreId) { + /* @var Collection $collection */ + $collection = $this->productCollectionFactory->create() + ->setStoreId($categoryStoreId) + ->addIdFilter($category->getAffectedProductIds()) + ->addAttributeToSelect('visibility') + ->addAttributeToSelect('name') + ->addAttributeToSelect('url_key') + ->addAttributeToSelect('url_path'); + + $collection->setPageSize(1000); + $pageCount = $collection->getLastPageNumber(); + $currentPage = 1; + while ($currentPage <= $pageCount) { + $collection->setCurPage($currentPage); + foreach ($collection as $product) { + $product->setData('save_rewrites_history', $saveRewriteHistory); + $product->setStoreId($categoryStoreId); + $mergeDataProvider->merge( + $this->productUrlRewriteGenerator->generate($product, $category->getEntityId()) + ); + } + $collection->clear(); + $currentPage++; + } } } + + /** + * Gets category store IDs without Global Store. + * + * @param Category $category + * @return array + */ + private function getCategoryStoreIds(Category $category): array + { + $ids = $category->getStoreIds(); + return array_filter($ids); + } } diff --git a/app/code/Magento/CatalogUrlRewrite/Plugin/Store/Block/Switcher.php b/app/code/Magento/CatalogUrlRewrite/Plugin/Store/Block/Switcher.php deleted file mode 100644 index 44213c007551c..0000000000000 --- a/app/code/Magento/CatalogUrlRewrite/Plugin/Store/Block/Switcher.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CatalogUrlRewrite\Plugin\Store\Block; - -use Magento\Framework\Data\Helper\PostHelper; -use Magento\Store\Api\StoreResolverInterface; -use Magento\Store\Model\Store; -use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -use Magento\Framework\App\Request\Http as HttpRequest; - -/** - * Plugin makes connection between Store and UrlRewrite modules - * because Magento\Store\Block\Switcher should not know about UrlRewrite module functionality. - */ -class Switcher -{ - /** - * @var PostHelper - */ - private $postHelper; - - /** - * @var UrlFinderInterface - */ - private $urlFinder; - - /** - * @var HttpRequest - */ - private $request; - - /** - * @param PostHelper $postHelper - * @param UrlFinderInterface $urlFinder - * @param HttpRequest $request - */ - public function __construct( - PostHelper $postHelper, - UrlFinderInterface $urlFinder, - HttpRequest $request - ) { - $this->postHelper = $postHelper; - $this->urlFinder = $urlFinder; - $this->request = $request; - } - - /** - * @param \Magento\Store\Block\Switcher $subject - * @param string $result - * @param Store $store - * @param array $data - * @return string - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterGetTargetStorePostData( - \Magento\Store\Block\Switcher $subject, - string $result, - Store $store, - array $data = [] - ): string { - $data[StoreResolverInterface::PARAM_NAME] = $store->getCode(); - $currentUrl = $store->getCurrentUrl(true); - $baseUrl = $store->getBaseUrl(); - $urlPath = parse_url($currentUrl, PHP_URL_PATH); - $urlToSwitch = $currentUrl; - - //check only catalog pages - if ($this->request->getFrontName() === 'catalog') { - $currentRewrite = $this->urlFinder->findOneByData([ - UrlRewrite::REQUEST_PATH => ltrim($urlPath, '/'), - UrlRewrite::STORE_ID => $store->getId(), - ]); - if (null === $currentRewrite) { - $urlToSwitch = $baseUrl; - } - } - - return $this->postHelper->getPostData($urlToSwitch, $data); - } -} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index 03c48982f5865..0000000000000 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index b32b0216b9bdf..7435096642de2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Test\Unit\Model; use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; @@ -32,7 +34,10 @@ class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ protected $category; - protected function setUp() + /** + * @inheritdoc + */ + protected function setUp(): void { $this->category = $this->createMock(\Magento\Catalog\Model\Category::class); $productMethods = [ @@ -69,7 +74,7 @@ protected function setUp() /** * @return array */ - public function getUrlPathDataProvider() + public function getUrlPathDataProvider(): array { return [ 'path based on url key' => ['url-key', null, 'url-key'], @@ -84,8 +89,9 @@ public function getUrlPathDataProvider() * @param string|null|bool $urlKey * @param string|null|bool $productName * @param string $result + * @return void */ - public function testGetUrlPath($urlKey, $productName, $result) + public function testGetUrlPath($urlKey, $productName, $result): void { $this->product->expects($this->once())->method('getData')->with('url_path') ->will($this->returnValue(null)); @@ -99,22 +105,23 @@ public function testGetUrlPath($urlKey, $productName, $result) /** * @param string|bool $productUrlKey * @param string|bool $expectedUrlKey + * @return void * @dataProvider getUrlKeyDataProvider */ - public function testGetUrlKey($productUrlKey, $expectedUrlKey) + public function testGetUrlKey($productUrlKey, $expectedUrlKey): void { $this->product->expects($this->any())->method('getUrlKey')->will($this->returnValue($productUrlKey)); $this->product->expects($this->any())->method('formatUrlKey')->will($this->returnValue($productUrlKey)); - $this->assertEquals($expectedUrlKey, $this->productUrlPathGenerator->getUrlKey($this->product)); + $this->assertSame($expectedUrlKey, $this->productUrlPathGenerator->getUrlKey($this->product)); } /** * @return array */ - public function getUrlKeyDataProvider() + public function getUrlKeyDataProvider(): array { return [ - 'URL Key use default' => [false, false], + 'URL Key use default' => [false, null], 'URL Key empty' => ['product-url', 'product-url'], ]; } @@ -123,9 +130,10 @@ public function getUrlKeyDataProvider() * @param string|null|bool $storedUrlKey * @param string|null|bool $productName * @param string $expectedUrlKey + * @return void * @dataProvider getUrlPathDefaultUrlKeyDataProvider */ - public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expectedUrlKey) + public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expectedUrlKey): void { $this->product->expects($this->once())->method('getData')->with('url_path') ->will($this->returnValue(null)); @@ -138,7 +146,7 @@ public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expect /** * @return array */ - public function getUrlPathDefaultUrlKeyDataProvider() + public function getUrlPathDefaultUrlKeyDataProvider(): array { return [ ['default-store-view-url-key', null, 'default-store-view-url-key'], @@ -146,7 +154,10 @@ public function getUrlPathDefaultUrlKeyDataProvider() ]; } - public function testGetUrlPathWithCategory() + /** + * @return void + */ + public function testGetUrlPathWithCategory(): void { $this->product->expects($this->once())->method('getData')->with('url_path') ->will($this->returnValue('product-path')); @@ -159,7 +170,10 @@ public function testGetUrlPathWithCategory() ); } - public function testGetUrlPathWithSuffix() + /** + * @return void + */ + public function testGetUrlPathWithSuffix(): void { $storeId = 1; $this->product->expects($this->once())->method('getData')->with('url_path') @@ -177,7 +191,10 @@ public function testGetUrlPathWithSuffix() ); } - public function testGetUrlPathWithSuffixAndCategoryAndStore() + /** + * @return void + */ + public function testGetUrlPathWithSuffixAndCategoryAndStore(): void { $storeId = 1; $this->product->expects($this->once())->method('getData')->with('url_path') @@ -195,7 +212,10 @@ public function testGetUrlPathWithSuffixAndCategoryAndStore() ); } - public function testGetCanonicalUrlPath() + /** + * @return void + */ + public function testGetCanonicalUrlPath(): void { $this->product->expects($this->once())->method('getId')->will($this->returnValue(1)); @@ -205,7 +225,10 @@ public function testGetCanonicalUrlPath() ); } - public function testGetCanonicalUrlPathWithCategory() + /** + * @return void + */ + public function testGetCanonicalUrlPathWithCategory(): void { $this->product->expects($this->once())->method('getId')->will($this->returnValue(1)); $this->category->expects($this->once())->method('getId')->will($this->returnValue(1)); diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductUrlKeyAutogeneratorObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductUrlKeyAutogeneratorObserverTest.php new file mode 100644 index 0000000000000..b9628caff8400 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/ProductUrlKeyAutogeneratorObserverTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Test\Unit\Observer; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; + +/** + * Unit tests for \Magento\CatalogUrlRewrite\Observer\ProductUrlKeyAutogeneratorObserver class + */ +class ProductUrlKeyAutogeneratorObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $productUrlPathGenerator; + + /** @var \Magento\CatalogUrlRewrite\Observer\ProductUrlKeyAutogeneratorObserver */ + private $productUrlKeyAutogeneratorObserver; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->productUrlPathGenerator = $this->getMockBuilder(ProductUrlPathGenerator::class) + ->disableOriginalConstructor() + ->setMethods(['getUrlKey']) + ->getMock(); + + $this->productUrlKeyAutogeneratorObserver = (new ObjectManagerHelper($this))->getObject( + \Magento\CatalogUrlRewrite\Observer\ProductUrlKeyAutogeneratorObserver::class, + [ + 'productUrlPathGenerator' => $this->productUrlPathGenerator + ] + ); + } + + /** + * @return void + */ + public function testExecuteWithUrlKey(): void + { + $urlKey = 'product_url_key'; + + $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['setUrlKey']) + ->getMock(); + $product->expects($this->atLeastOnce())->method('setUrlKey')->with($urlKey); + $event = $this->getMockBuilder(\Magento\Framework\Event::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMock(); + $event->expects($this->atLeastOnce())->method('getProduct')->willReturn($product); + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $observer */ + $observer = $this->getMockBuilder(\Magento\Framework\Event\Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getEvent']) + ->getMock(); + $observer->expects($this->atLeastOnce())->method('getEvent')->willReturn($event); + $this->productUrlPathGenerator->expects($this->atLeastOnce())->method('getUrlKey')->with($product) + ->willReturn($urlKey); + + $this->productUrlKeyAutogeneratorObserver->execute($observer); + } + + /** + * @return void + */ + public function testExecuteWithEmptyUrlKey(): void + { + $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['setUrlKey']) + ->getMock(); + $product->expects($this->never())->method('setUrlKey'); + $event = $this->getMockBuilder(\Magento\Framework\Event::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMock(); + $event->expects($this->atLeastOnce())->method('getProduct')->willReturn($product); + /** @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject $observer */ + $observer = $this->getMockBuilder(\Magento\Framework\Event\Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getEvent']) + ->getMock(); + $observer->expects($this->atLeastOnce())->method('getEvent')->willReturn($event); + $this->productUrlPathGenerator->expects($this->atLeastOnce())->method('getUrlKey')->with($product) + ->willReturn(null); + + $this->productUrlKeyAutogeneratorObserver->execute($observer); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/etc/db_schema_whitelist.json b/app/code/Magento/CatalogUrlRewrite/etc/db_schema_whitelist.json index f2e486e32eda1..5562ae5d48cc9 100644 --- a/app/code/Magento/CatalogUrlRewrite/etc/db_schema_whitelist.json +++ b/app/code/Magento/CatalogUrlRewrite/etc/db_schema_whitelist.json @@ -1,19 +1,19 @@ { - "catalog_url_rewrite_product_category": { - "column": { - "url_rewrite_id": true, - "category_id": true, - "product_id": true - }, - "index": { - "CATALOG_URL_REWRITE_PRODUCT_CATEGORY_CATEGORY_ID_PRODUCT_ID": true - }, - "constraint": { - "CAT_URL_REWRITE_PRD_CTGR_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "FK_BB79E64705D7F17FE181F23144528FC8": true, - "CAT_URL_REWRITE_PRD_CTGR_CTGR_ID_CAT_CTGR_ENTT_ENTT_ID": true, - "CAT_URL_REWRITE_PRD_CTGR_CTGR_ID_SEQUENCE_CAT_CTGR_SEQUENCE_VAL": true, - "CAT_URL_REWRITE_PRD_CTGR_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + "catalog_url_rewrite_product_category": { + "column": { + "url_rewrite_id": true, + "category_id": true, + "product_id": true + }, + "index": { + "CATALOG_URL_REWRITE_PRODUCT_CATEGORY_CATEGORY_ID_PRODUCT_ID": true + }, + "constraint": { + "CAT_URL_REWRITE_PRD_CTGR_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "FK_BB79E64705D7F17FE181F23144528FC8": true, + "CAT_URL_REWRITE_PRD_CTGR_CTGR_ID_CAT_CTGR_ENTT_ENTT_ID": true, + "CAT_URL_REWRITE_PRD_CTGR_CTGR_ID_SEQUENCE_CAT_CTGR_SEQUENCE_VAL": true, + "CAT_URL_REWRITE_PRD_CTGR_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml deleted file mode 100644 index 3a9122b2f748d..0000000000000 --- a/app/code/Magento/CatalogUrlRewrite/etc/frontend/di.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="\Magento\Store\Block\Switcher"> - <plugin name="store_switcher_plugin" type="Magento\CatalogUrlRewrite\Plugin\Store\Block\Switcher"/> - </type> -</config> diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Test/Mftf/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/Test/Mftf/composer.json deleted file mode 100644 index a66ca925561c1..0000000000000 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-url-rewrite-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev", - "magento/functional-test-module-url-rewrite-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php index f22879df0ae0c..e5fb20a58aea1 100644 --- a/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php +++ b/app/code/Magento/CatalogWidget/Model/Rule/Condition/Product.php @@ -9,6 +9,7 @@ */ namespace Magento\CatalogWidget\Model\Rule\Condition; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\ProductCategoryList; /** @@ -77,17 +78,22 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function loadAttributeOptions() { $productAttributes = $this->_productResource->loadAllAttributes()->getAttributesByCode(); + $productAttributes = array_filter( + $productAttributes, + function ($attribute) { + return $attribute->getFrontendLabel() && + $attribute->getFrontendInput() !== 'text' && + $attribute->getAttributeCode() !== ProductInterface::STATUS; + } + ); $attributes = []; foreach ($productAttributes as $attribute) { - if (!$attribute->getFrontendLabel() || $attribute->getFrontendInput() == 'text') { - continue; - } $attributes[$attribute->getAttributeCode()] = $attribute->getFrontendLabel(); } @@ -101,6 +107,9 @@ public function loadAttributeOptions() /** * {@inheritdoc} + * + * @param array &$attributes + * @return void */ protected function _addSpecialAttributes(array &$attributes) { @@ -163,8 +172,6 @@ protected function addGlobalAttribute( \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute, \Magento\Catalog\Model\ResourceModel\Product\Collection $collection ) { - $storeId = $this->storeManager->getStore()->getId(); - switch ($attribute->getBackendType()) { case 'decimal': case 'datetime': @@ -173,10 +180,15 @@ protected function addGlobalAttribute( $collection->addAttributeToSelect($attribute->getAttributeCode(), 'inner'); break; default: - $alias = 'at_' . md5($this->getId()) . $attribute->getAttributeCode(); + $alias = 'at_' . sha1($this->getId()) . $attribute->getAttributeCode(); + + $connection = $this->_productResource->getConnection(); + $storeId = $connection->getIfNullSql($alias . '.store_id', $this->storeManager->getStore()->getId()); + $linkField = $attribute->getEntity()->getLinkField(); + $collection->getSelect()->join( - [$alias => $collection->getTable('catalog_product_index_eav')], - "($alias.entity_id = e.entity_id) AND ($alias.store_id = $storeId)" . + [$alias => $collection->getTable('catalog_product_entity_varchar')], + "($alias.$linkField = e.$linkField) AND ($alias.store_id = $storeId)" . " AND ($alias.attribute_id = {$attribute->getId()})", [] ); @@ -225,6 +237,8 @@ protected function addNotGlobalAttribute( /** * {@inheritdoc} + * + * @return string */ public function getMappedSqlField() { @@ -244,6 +258,9 @@ public function getMappedSqlField() /** * {@inheritdoc} + * + * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection + * @return $this */ public function collectValidatedAttributes($productCollection) { diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/composer.json b/app/code/Magento/CatalogWidget/Test/Mftf/composer.json deleted file mode 100644 index 9b3437526718a..0000000000000 --- a/app/code/Magento/CatalogWidget/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-widget", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php index 09270b6b41fc7..219cae6829299 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -17,11 +17,21 @@ class ProductTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * @var \Magento\Catalog\Model\ResourceModel\Product|\PHPUnit_Framework_MockObject_MockObject + */ + private $productResource; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ private $attributeMock; + /** + * @inheritdoc + * + * @return void + */ protected function setUp() { $objectManagerHelper = new ObjectManager($this); @@ -33,9 +43,9 @@ protected function setUp() $storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); $storeManager->expects($this->any())->method('getStore')->willReturn($storeMock); - $productResource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); - $productResource->expects($this->once())->method('loadAllAttributes')->willReturnSelf(); - $productResource->expects($this->once())->method('getAttributesByCode')->willReturn([]); + $this->productResource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class); + $this->productResource->expects($this->once())->method('loadAllAttributes')->willReturnSelf(); + $this->productResource->expects($this->once())->method('getAttributesByCode')->willReturn([]); $productCategoryList = $this->getMockBuilder(\Magento\Catalog\Model\ProductCategoryList::class) ->disableOriginalConstructor() ->getMock(); @@ -45,7 +55,7 @@ protected function setUp() [ 'config' => $eavConfig, 'storeManager' => $storeManager, - 'productResource' => $productResource, + 'productResource' => $this->productResource, 'productCategoryList' => $productCategoryList, 'data' => [ 'rule' => $ruleMock, @@ -55,6 +65,11 @@ protected function setUp() ); } + /** + * Test addToCollection method. + * + * @return void + */ public function testAddToCollection() { $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); @@ -67,9 +82,22 @@ public function testAddToCollection() $this->attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(true); $this->attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(true); $this->attributeMock->expects($this->once())->method('getBackendType')->willReturn('multiselect'); + + $entityMock = $this->createMock(\Magento\Eav\Model\Entity\AbstractEntity::class); + $entityMock->expects($this->once())->method('getLinkField')->willReturn('entitiy_id'); + $this->attributeMock->expects($this->once())->method('getEntity')->willReturn($entityMock); + $connection = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + + $this->productResource->expects($this->atLeastOnce())->method('getConnection')->willReturn($connection); + $this->model->addToCollection($collectionMock); } + /** + * Test getMappedSqlField method. + * + * @return void + */ public function testGetMappedSqlFieldSku() { $this->model->setAttribute('sku'); diff --git a/app/code/Magento/CatalogWidget/etc/widget.xml b/app/code/Magento/CatalogWidget/etc/widget.xml index bcc1b623da02e..1e6ed19057171 100644 --- a/app/code/Magento/CatalogWidget/etc/widget.xml +++ b/app/code/Magento/CatalogWidget/etc/widget.xml @@ -33,7 +33,7 @@ <parameter name="template" xsi:type="select" required="true" visible="true"> <label translate="true">Template</label> <options> - <option name="default" value="product/widget/content/grid.phtml" selected="true"> + <option name="default" value="Magento_CatalogWidget::product/widget/content/grid.phtml" selected="true"> <label translate="true">Products Grid Template</label> </option> </options> diff --git a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php index 06be39d0f3516..a43f074d8df67 100644 --- a/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php +++ b/app/code/Magento/Checkout/Block/Cart/Item/Renderer.php @@ -11,6 +11,8 @@ use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Quote\Model\Quote\Item\AbstractItem; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; /** * Shopping cart item render block @@ -21,6 +23,7 @@ * @method \Magento\Checkout\Block\Cart\Item\Renderer setProductName(string) * @method \Magento\Checkout\Block\Cart\Item\Renderer setDeleteUrl(string) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Renderer extends \Magento\Framework\View\Element\Template implements \Magento\Framework\DataObject\IdentityInterface @@ -91,6 +94,9 @@ class Renderer extends \Magento\Framework\View\Element\Template implements */ private $messageInterpretationStrategy; + /** @var ItemResolverInterface */ + private $itemResolver; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Catalog\Helper\Product\Configuration $productConfig @@ -102,6 +108,7 @@ class Renderer extends \Magento\Framework\View\Element\Template implements * @param \Magento\Framework\Module\Manager $moduleManager * @param InterpretationStrategyInterface $messageInterpretationStrategy * @param array $data + * @param ItemResolverInterface|null $itemResolver * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @codeCoverageIgnore */ @@ -115,7 +122,8 @@ public function __construct( PriceCurrencyInterface $priceCurrency, \Magento\Framework\Module\Manager $moduleManager, InterpretationStrategyInterface $messageInterpretationStrategy, - array $data = [] + array $data = [], + ItemResolverInterface $itemResolver = null ) { $this->priceCurrency = $priceCurrency; $this->imageBuilder = $imageBuilder; @@ -127,6 +135,7 @@ public function __construct( $this->_isScopePrivate = true; $this->moduleManager = $moduleManager; $this->messageInterpretationStrategy = $messageInterpretationStrategy; + $this->itemResolver = $itemResolver ?: ObjectManager::getInstance()->get(ItemResolverInterface::class); } /** @@ -172,7 +181,7 @@ public function getProduct() */ public function getProductForThumbnail() { - return $this->getProduct(); + return $this->itemResolver->getFinalProduct($this->getItem()); } /** diff --git a/app/code/Magento/Checkout/Controller/Cart/Add.php b/app/code/Magento/Checkout/Controller/Cart/Add.php index 92dd8dd8f251c..5f54ca3395646 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Add.php +++ b/app/code/Magento/Checkout/Controller/Cart/Add.php @@ -80,6 +80,9 @@ protected function _initProduct() public function execute() { if (!$this->_formKeyValidator->validate($this->getRequest())) { + $this->messageManager->addErrorMessage( + __('Your session has expired') + ); return $this->resultRedirectFactory->create()->setPath('*/*/'); } diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php new file mode 100644 index 0000000000000..ac4a93e6066a4 --- /dev/null +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Controller\Cart; + +use Magento\Checkout\Model\Cart\RequestQuantityProcessor; +use Magento\Framework\App\Action\Context; +use Magento\Framework\Exception\LocalizedException; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; +use Magento\Quote\Model\Quote\Item; +use Psr\Log\LoggerInterface; + +class UpdateItemQty extends \Magento\Framework\App\Action\Action +{ + /** + * @var RequestQuantityProcessor + */ + private $quantityProcessor; + + /** + * @var FormKeyValidator + */ + private $formKeyValidator; + + /** + * @var CheckoutSession + */ + private $checkoutSession; + + /** + * @var Json + */ + private $json; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param Context $context, + * @param RequestQuantityProcessor $quantityProcessor + * @param FormKeyValidator $formKeyValidator + * @param CheckoutSession $checkoutSession + * @param Json $json + * @param LoggerInterface $logger + */ + public function __construct( + Context $context, + RequestQuantityProcessor $quantityProcessor, + FormKeyValidator $formKeyValidator, + CheckoutSession $checkoutSession, + Json $json, + LoggerInterface $logger + ) { + $this->quantityProcessor = $quantityProcessor; + $this->formKeyValidator = $formKeyValidator; + $this->checkoutSession = $checkoutSession; + $this->json = $json; + $this->logger = $logger; + parent::__construct($context); + } + + /** + * @return void + */ + public function execute() + { + try { + if (!$this->formKeyValidator->validate($this->getRequest())) { + throw new LocalizedException( + __('Something went wrong while saving the page. Please refresh the page and try again.') + ); + } + + $cartData = $this->getRequest()->getParam('cart'); + if (!is_array($cartData)) { + throw new LocalizedException( + __('Something went wrong while saving the page. Please refresh the page and try again.') + ); + } + + $cartData = $this->quantityProcessor->process($cartData); + $quote = $this->checkoutSession->getQuote(); + + foreach ($cartData as $itemId => $itemInfo) { + $item = $quote->getItemById($itemId); + $qty = isset($itemInfo['qty']) ? (double)$itemInfo['qty'] : 0; + if ($item) { + $this->updateItemQuantity($item, $qty); + } + } + + $this->jsonResponse(); + } catch (LocalizedException $e) { + $this->jsonResponse($e->getMessage()); + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); + $this->jsonResponse('Something went wrong while saving the page. Please refresh the page and try again.'); + } + } + + /** + * Updates quote item quantity. + * + * @param Item $item + * @param float $qty + * @throws LocalizedException + */ + private function updateItemQuantity(Item $item, float $qty) + { + if ($qty > 0) { + $item->setQty($qty); + + if ($item->getHasError()) { + throw new LocalizedException(__($item->getMessage())); + } + } + } + + /** + * JSON response builder. + * + * @param string $error + * @return void + */ + private function jsonResponse(string $error = '') + { + $this->getResponse()->representJson( + $this->json->serialize($this->getResponseData($error)) + ); + } + + /** + * Returns response data. + * + * @param string $error + * @return array + */ + private function getResponseData(string $error = ''): array + { + $response = [ + 'success' => true, + ]; + + if (!empty($error)) { + $response = [ + 'success' => false, + 'error_message' => $error, + ]; + } + + return $response; + } +} diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php b/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php index 174cb38b0e9a9..05ce48c9b46ce 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdatePost.php @@ -6,8 +6,45 @@ */ namespace Magento\Checkout\Controller\Cart; +use Magento\Checkout\Model\Cart\RequestQuantityProcessor; + class UpdatePost extends \Magento\Checkout\Controller\Cart { + /** + * @var RequestQuantityProcessor + */ + private $quantityProcessor; + + /** + * @param \Magento\Framework\App\Action\Context $context + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator + * @param \Magento\Checkout\Model\Cart $cart + * @param RequestQuantityProcessor $quantityProcessor + */ + public function __construct( + \Magento\Framework\App\Action\Context $context, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator, + \Magento\Checkout\Model\Cart $cart, + RequestQuantityProcessor $quantityProcessor = null + ) { + parent::__construct( + $context, + $scopeConfig, + $checkoutSession, + $storeManager, + $formKeyValidator, + $cart + ); + + $this->quantityProcessor = $quantityProcessor ?: $this->_objectManager->get(RequestQuantityProcessor::class); + } + /** * Empty customer's shopping cart * @@ -34,20 +71,10 @@ protected function _updateShoppingCart() try { $cartData = $this->getRequest()->getParam('cart'); if (is_array($cartData)) { - $filter = new \Zend_Filter_LocalizedToNormalized( - ['locale' => $this->_objectManager->get( - \Magento\Framework\Locale\ResolverInterface::class - )->getLocale()] - ); - foreach ($cartData as $index => $data) { - if (isset($data['qty'])) { - $cartData[$index]['qty'] = $filter->filter(trim($data['qty'])); - } - } if (!$this->cart->getCustomerSession()->getCustomerId() && $this->cart->getQuote()->getCustomerId()) { $this->cart->getQuote()->setCustomerId(null); } - + $cartData = $this->quantityProcessor->process($cartData); $cartData = $this->cart->suggestItemsQty($cartData); $this->cart->updateItems($cartData)->save(); } diff --git a/app/code/Magento/Checkout/CustomerData/DefaultItem.php b/app/code/Magento/Checkout/CustomerData/DefaultItem.php index 9351685405a60..21580d1275d0c 100644 --- a/app/code/Magento/Checkout/CustomerData/DefaultItem.php +++ b/app/code/Magento/Checkout/CustomerData/DefaultItem.php @@ -7,6 +7,7 @@ namespace Magento\Checkout\CustomerData; use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; /** * Default item @@ -39,12 +40,15 @@ class DefaultItem extends AbstractItem protected $checkoutHelper; /** - * Escaper - * * @var \Magento\Framework\Escaper */ private $escaper; + /** + * @var ItemResolverInterface + */ + private $itemResolver; + /** * @param \Magento\Catalog\Helper\Image $imageHelper * @param \Magento\Msrp\Helper\Data $msrpHelper @@ -52,6 +56,7 @@ class DefaultItem extends AbstractItem * @param \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool * @param \Magento\Checkout\Helper\Data $checkoutHelper * @param \Magento\Framework\Escaper|null $escaper + * @param ItemResolverInterface|null $itemResolver * @codeCoverageIgnore */ public function __construct( @@ -60,7 +65,8 @@ public function __construct( \Magento\Framework\UrlInterface $urlBuilder, \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool, \Magento\Checkout\Helper\Data $checkoutHelper, - \Magento\Framework\Escaper $escaper = null + \Magento\Framework\Escaper $escaper = null, + ItemResolverInterface $itemResolver = null ) { $this->configurationPool = $configurationPool; $this->imageHelper = $imageHelper; @@ -68,6 +74,7 @@ public function __construct( $this->urlBuilder = $urlBuilder; $this->checkoutHelper = $checkoutHelper; $this->escaper = $escaper ?: ObjectManager::getInstance()->get(\Magento\Framework\Escaper::class); + $this->itemResolver = $itemResolver ?: ObjectManager::getInstance()->get(ItemResolverInterface::class); } /** @@ -119,7 +126,7 @@ protected function getOptionList() */ protected function getProductForThumbnail() { - return $this->getProduct(); + return $this->itemResolver->getFinalProduct($this->item); } /** diff --git a/app/code/Magento/Checkout/Model/Cart/ImageProvider.php b/app/code/Magento/Checkout/Model/Cart/ImageProvider.php index d8d0003d8ca7e..cdadf3573c8ec 100644 --- a/app/code/Magento/Checkout/Model/Cart/ImageProvider.php +++ b/app/code/Magento/Checkout/Model/Cart/ImageProvider.php @@ -5,8 +5,10 @@ */ namespace Magento\Checkout\Model\Cart; +use Magento\Checkout\CustomerData\DefaultItem; +use Magento\Framework\App\ObjectManager; + /** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api */ class ImageProvider @@ -18,20 +20,27 @@ class ImageProvider /** * @var \Magento\Checkout\CustomerData\ItemPoolInterface + * @deprecated No need for the pool as images are resolved in the default item implementation + * @see \Magento\Checkout\CustomerData\DefaultItem::getProductForThumbnail */ protected $itemPool; + /** @var \Magento\Checkout\CustomerData\DefaultItem */ + protected $customerDataItem; + /** * @param \Magento\Quote\Api\CartItemRepositoryInterface $itemRepository * @param \Magento\Checkout\CustomerData\ItemPoolInterface $itemPool - * @codeCoverageIgnore + * @param DefaultItem|null $customerDataItem */ public function __construct( \Magento\Quote\Api\CartItemRepositoryInterface $itemRepository, - \Magento\Checkout\CustomerData\ItemPoolInterface $itemPool + \Magento\Checkout\CustomerData\ItemPoolInterface $itemPool, + \Magento\Checkout\CustomerData\DefaultItem $customerDataItem = null ) { $this->itemRepository = $itemRepository; $this->itemPool = $itemPool; + $this->customerDataItem = $customerDataItem ?: ObjectManager::getInstance()->get(DefaultItem::class); } /** @@ -45,7 +54,7 @@ public function getImages($cartId) $items = $this->itemRepository->getList($cartId); /** @var \Magento\Quote\Model\Quote\Item $cartItem */ foreach ($items as $cartItem) { - $allData = $this->itemPool->getItemData($cartItem); + $allData = $this->customerDataItem->getItemData($cartItem); $itemData[$cartItem->getItemId()] = $allData['product_image']; } return $itemData; diff --git a/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php new file mode 100644 index 0000000000000..971b35c8f3e3d --- /dev/null +++ b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Model\Cart; + +use Magento\Framework\Locale\ResolverInterface; + +class RequestQuantityProcessor +{ + /** + * @var ResolverInterface + */ + private $localeResolver; + + /** + * RequestQuantityProcessor constructor. + * @param ResolverInterface $localeResolver + */ + public function __construct( + ResolverInterface $localeResolver + ) { + $this->localeResolver = $localeResolver; + } + + /** + * Process cart request data + * + * @param array $cartData + * @return array + */ + public function process(array $cartData): array + { + $filter = new \Zend\I18n\Filter\NumberParse($this->localeResolver->getLocale()); + + foreach ($cartData as $index => $data) { + if (isset($data['qty'])) { + $data['qty'] = is_string($data['qty']) ? trim($data['qty']) : $data['qty']; + $cartData[$index]['qty'] = $filter->filter($data['qty']); + } + } + + return $cartData; + } +} diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index b5727bf8f365e..16f13511001e9 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -8,12 +8,14 @@ use Magento\Catalog\Helper\Product\ConfigurationPool; use Magento\Checkout\Helper\Data as CheckoutHelper; use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; use Magento\Customer\Model\Context as CustomerContext; use Magento\Customer\Model\Session as CustomerSession; use Magento\Customer\Model\Url as CustomerUrlManager; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Http\Context as HttpContext; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Locale\FormatInterface as LocaleFormat; use Magento\Framework\UrlInterface; @@ -159,6 +161,11 @@ class DefaultConfigProvider implements ConfigProviderInterface */ protected $urlBuilder; + /** + * @var AddressMetadataInterface + */ + private $addressMetadata; + /** * @param CheckoutHelper $checkoutHelper * @param Session $checkoutSession @@ -186,6 +193,7 @@ class DefaultConfigProvider implements ConfigProviderInterface * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement * @param UrlInterface $urlBuilder + * @param AddressMetadataInterface $addressMetadata * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -215,7 +223,8 @@ public function __construct( \Magento\Shipping\Model\Config $shippingMethodConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement, - UrlInterface $urlBuilder + UrlInterface $urlBuilder, + AddressMetadataInterface $addressMetadata = null ) { $this->checkoutHelper = $checkoutHelper; $this->checkoutSession = $checkoutSession; @@ -243,6 +252,7 @@ public function __construct( $this->storeManager = $storeManager; $this->paymentMethodManagement = $paymentMethodManagement; $this->urlBuilder = $urlBuilder; + $this->addressMetadata = $addressMetadata ?: ObjectManager::getInstance()->get(AddressMetadataInterface::class); } /** @@ -324,11 +334,34 @@ private function getCustomerData() $customerData = $customer->__toArray(); foreach ($customer->getAddresses() as $key => $address) { $customerData['addresses'][$key]['inline'] = $this->getCustomerAddressInline($address); + if ($address->getCustomAttributes()) { + $customerData['addresses'][$key]['custom_attributes'] = $this->filterNotVisibleAttributes( + $customerData['addresses'][$key]['custom_attributes'] + ); + } } } return $customerData; } + /** + * Filter not visible on storefront custom attributes. + * + * @param array $attributes + * @return array + */ + private function filterNotVisibleAttributes(array $attributes) + { + $attributesMetadata = $this->addressMetadata->getAllAttributesMetadata(); + foreach ($attributesMetadata as $attributeMetadata) { + if (!$attributeMetadata->isVisible()) { + unset($attributes[$attributeMetadata->getAttributeCode()]); + } + } + + return $attributes; + } + /** * Set additional customer address data * diff --git a/app/code/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddresses.php b/app/code/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddresses.php new file mode 100644 index 0000000000000..3791b28917e97 --- /dev/null +++ b/app/code/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddresses.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Plugin\Model\Quote; + +use Magento\Quote\Model\Quote; + +/** + * Clear quote addresses after all items were removed. + */ +class ResetQuoteAddresses +{ + /** + * @param Quote $quote + * @param Quote $result + * @param mixed $itemId + * + * @return Quote + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterRemoveItem(Quote $quote, Quote $result, $itemId): Quote + { + if (empty($result->getAllVisibleItems())) { + foreach ($result->getAllAddresses() as $address) { + $result->removeAddress($address->getId()); + } + } + + return $result; + } +} diff --git a/app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php b/app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php index bc38809d070b2..47a19fb3234fd 100644 --- a/app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php +++ b/app/code/Magento/Checkout/Setup/Patch/Data/PrepareInitialCheckoutConfiguration.php @@ -817,7 +817,7 @@ public function apply() $connection->commit(); } catch (\Exception $e) { - $connection->rollback(); + $connection->rollBack(); throw $e; } } diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml index e70bccbfdfe2b..ebb1e81620fac 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Go to checkout from minicart --> <actionGroup name="GoToCheckoutFromMinicartActionGroup"> <wait stepKey="wait" time="10" /> @@ -93,13 +93,13 @@ <argument name="customerAddressVar"/> </arguments> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> - <see userInput="{{customerVar.firstname}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationFirstName"/> - <see userInput="{{customerVar.lastname}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationLastName"/> - <see userInput="{{customerAddressVar.street[0]}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationStreet"/> - <see userInput="{{customerAddressVar.city}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationCity"/> - <see userInput="{{customerAddressVar.state}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationState"/> - <see userInput="{{customerAddressVar.postcode}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationPostcode"/> - <see userInput="{{customerAddressVar.telephone}}" selector="{{CheckoutPaymentSection.shipToInfomation}}" stepKey="assertShipToInformationTelephone"/> + <see userInput="{{customerVar.firstname}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationFirstName"/> + <see userInput="{{customerVar.lastname}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationLastName"/> + <see userInput="{{customerAddressVar.street[0]}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationStreet"/> + <see userInput="{{customerAddressVar.city}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationCity"/> + <see userInput="{{customerAddressVar.state}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationState"/> + <see userInput="{{customerAddressVar.postcode}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationPostcode"/> + <see userInput="{{customerAddressVar.telephone}}" selector="{{CheckoutPaymentSection.shipToInformation}}" stepKey="assertShipToInformationTelephone"/> </actionGroup> <!-- Check shipping method in checkout --> @@ -108,7 +108,7 @@ <argument name="shippingMethod"/> </arguments> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> - <see userInput="{{shippingMethod}}" selector="{{CheckoutPaymentSection.shippingMethodInfomation}}" stepKey="assertshippingMethodInfomation"/> + <see userInput="{{shippingMethod}}" selector="{{CheckoutPaymentSection.shippingMethodInformation}}" stepKey="assertshippingMethodInformation"/> </actionGroup> <!-- Checkout select Check/Money Order payment --> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml index e7a6e219d28b1..5a71b82c15cdb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Guest checkout filling billing section --> <actionGroup name="GuestCheckoutFillNewBillingAddressActionGroup"> <arguments> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml new file mode 100644 index 0000000000000..6e5f127eefc18 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Assert That Shipping And Billing Address are the same--> + <actionGroup name="AssertThatShippingAndBillingAddressTheSame"> + <!--Get shipping and billing addresses--> + <grabTextFrom selector="{{ShipmentFormSection.shippingAddress}}" stepKey="shippingAddr"/> + <grabTextFrom selector="{{ShipmentFormSection.billingAddress}}" stepKey="billingAddr"/> + <!--Make sure that shipping and billing addresses are different--> + <see userInput="Shipping Address" stepKey="seeShippingAddress"/> + <see userInput="Billing Address" stepKey="seeBillingAddress"/> + <assertEquals stepKey="assert" actual="$billingAddr" expected="$shippingAddr"/> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml index b2402d94723cd..cb53a2a4f381f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="clickViewAndEditCartFromMiniCart"> <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart"/> <waitForElementVisible selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="waitForViewAndEditCartVisible"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index 35ddfdaefe050..29a76675fa52e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Add Product to Cart from the category page and check message and product count in Minicart --> <actionGroup name="StorefrontAddCategoryProductToCartActionGroup"> <arguments> @@ -21,6 +21,24 @@ <waitForText userInput="{{productCount}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> </actionGroup> + <!-- Add Product to Cart from the category page with specified quantity and check message and product count in Minicart --> + <actionGroup name="StorefrontAddCategoryProductToCartWithQuantityActionGroup"> + <arguments> + <argument name="product"/> + <argument name="quantity" defaultValue="1" type="string"/> + <argument name="checkQuantity" defaultValue="1" type="string"/> + </arguments> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductInfoByName(product.name)}}" stepKey="moveMouseOverProduct" /> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="clickAddToCart" /> + <!-- @TODO: Use general message selector after MQE-694 is fixed --> + <waitForElement selector="{{StorefrontMessagesSection.messageProductAddedToCart(product.name)}}" time="30" stepKey="assertMessage"/> + <waitForText userInput="{{checkQuantity}}" selector="{{StorefrontMinicartSection.productCount}}" time="30" stepKey="assertProductCount"/> + <conditionalClick selector="{{StorefrontMinicartSection.showCart}}" dependentSelector="{{StorefrontMinicartSection.miniCartOpened}}" visible="false" stepKey="openMiniCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="waitForViewAndEditCartVisible"/> + <fillField selector="{{StorefrontMinicartSection.itemQuantity(product.name)}}" userInput="{{quantity}}" stepKey="setProductQtyToFiftyInMiniCart"/> + <click selector="{{StorefrontMinicartSection.itemQuantityUpdate(product.name)}}" stepKey="updateQtyInMiniCart"/> + </actionGroup> + <!-- Add Product to Cart from the product page and check message and product count in Minicart --> <actionGroup name="StorefrontAddProductToCartActionGroup"> <arguments> @@ -84,7 +102,7 @@ </arguments> <conditionalClick stepKey="openShippingDetails" selector="{{CheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false"/> <selectOption stepKey="selectCountry" selector="{{CheckoutCartSummarySection.country}}" userInput="{{taxCode.country}}"/> - <selectOption stepKey="selectStateProvince" selector="{{CheckoutCartSummarySection.country}}" userInput="{{taxCode.country}}"/> + <selectOption stepKey="selectStateProvince" selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{taxCode.state}}"/> <fillField stepKey="fillZip" selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{taxCode.zip}}"/> <waitForPageLoad stepKey="waitForFormUpdate"/> </actionGroup> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/ConstData.xml index 1703d7255e1f8..f946d04fc9f95 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Data/ConstData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="CONST" type="CONST"> <data key="successGuestCheckoutOrderNumberMessage">Your order # is:</data> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml index 51fb264a16a1c..530157851191f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="E2EB2CQuote" type="Quote"> <data key="subtotal">480.00</data> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml index dcc652829b3cc..b0acc64c77727 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml @@ -7,8 +7,8 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="CheckoutCartPage" url="/checkout/cart" module="Checkout" area="storefront"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="CheckoutCartPage" url="/checkout/cart" module="Magento_Checkout" area="storefront"> <section name="CheckoutCartProductSection"/> <section name="CheckoutCartSummarySection"/> </page> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml index aa11d42275a38..d3fa045e4654f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CheckoutPage" url="/checkout" area="storefront" module="Magento_Checkout"> <section name="CheckoutShippingSection"/> <section name="CheckoutShippingMethodsSection"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutShippingPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutShippingPage.xml new file mode 100644 index 0000000000000..07f9e9e6481f7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutShippingPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="CheckoutShippingPage" url="/checkout/#shipping" module="Checkout" area="storefront"> + <section name="CheckoutShippingGuestInfoSection"/> + <section name="CheckoutShippingSection"/> + </page> +</pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutSuccessPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutSuccessPage.xml index 1c3293267e2ab..ebca0651b457d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutSuccessPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutSuccessPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CheckoutSuccessPage" url="/checkout/onepage/success/" area="storefront" module="Magento_Checkout"> <section name="CheckoutSuccessMainSection"/> <section name="CheckoutSuccessRegisterSection"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/GuestCheckoutReviewAndPaymentsPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/GuestCheckoutReviewAndPaymentsPage.xml index cac9c934cd662..90f8a914b4f42 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/GuestCheckoutReviewAndPaymentsPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/GuestCheckoutReviewAndPaymentsPage.xml @@ -6,7 +6,7 @@ */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="GuestCheckoutReviewAndPaymentsPage" url="/checkout/#payment" area="storefront" module="Magento_Checkout"> <section name="CheckoutPaymentSection"/> </page> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/AdminDataGridHeaderSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/AdminDataGridHeaderSection.xml new file mode 100644 index 0000000000000..3e1f902f6c3be --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/AdminDataGridHeaderSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminDataGridHeaderSection"> + <element name="attributeCodeFilterInput" type="input" selector=".admin__data-grid-filters input[name='attribute_code']"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml index 6e2262d58b89d..ab82d9fdd93b5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutCartProductSection"> <element name="ProductLinkByName" type="button" selector="//main//table[@id='shopping-cart-table']//tbody//tr//strong[contains(@class, 'product-item-name')]//a[contains(text(), '{{var1}}')]" @@ -29,5 +29,7 @@ <element name="nthItemOption" type="block" selector=".item:nth-of-type({{numElement}}) .item-options" parameterized="true"/> <element name="nthEditButton" type="block" selector=".item:nth-of-type({{numElement}}) .action-edit" parameterized="true"/> <element name="nthBundleOptionName" type="text" selector=".product-item-details .item-options:nth-of-type({{numOption}}) dt" parameterized="true"/> + <element name="productSubtotalByName" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'subtotal')]//span[@class='price']" parameterized="true"/> + <element name="updateShoppingCartButton" type="button" selector="#form-validate button[type='submit'].update" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml index ce4069a0c916d..4eb396f4d10eb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutCartSummarySection"> <element name="subtotal" type="text" selector="//*[@id='cart-totals']//tr[@class='totals sub']//td//span[@class='price']"/> <element name="shippingMethod" type="text" selector="//*[@id='cart-totals']//tr[@class='totals shipping excl']//th//span[@class='value']"/> @@ -16,8 +16,10 @@ <element name="proceedToCheckout" type="button" selector=".action.primary.checkout span" timeout="30"/> <element name="discountAmount" type="text" selector="td[data-th='Discount']"/> <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> - <element name="postcode" type="input" selector="input[name='postcode']"/> - <element name="stateProvince" type="select" selector="select[name='region_id']"/> - <element name="country" type="select" selector="select[name='country_id']"/> + <element name="postcode" type="input" selector="input[name='postcode']" timeout="10"/> + <element name="stateProvince" type="select" selector="select[name='region_id']" timeout="10"/> + <element name="country" type="select" selector="select[name='country_id']" timeout="10"/> + <element name="estimateShippingAndTax" type="text" selector="#block-shipping-heading" timeout="5"/> + <element name="flatRateShippingMethod" type="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutHeaderSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutHeaderSection.xml index ca42eff89cf26..babbe51746df8 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutHeaderSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutHeaderSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutHeaderSection"> <element name="shippingMethodStep" type="text" selector=".opc-progress-bar-item:nth-of-type(1)"/> <element name="reviewAndPaymentsStep" type="text" selector=".opc-progress-bar-item:nth-of-type(2)"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml index cb079d2f0361e..3074f390306b1 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutOrderSummarySection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutOrderSummarySection"> <element name="miniCartTab" type="button" selector=".title[role='tab']"/> <element name="productItemName" type="text" selector=".product-item-name"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 6d7533be7e9ea..259f672ae8163 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -7,12 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutPaymentSection"> <element name="isPaymentSection" type="text" selector="//*[@class='opc-progress-bar']/li[contains(@class, '_active') and span[contains(.,'Review & Payments')]]"/> <element name="availablePaymentSolutions" type="text" selector="#checkout-payment-method-load>div>div>div:nth-child(2)>div.payment-method-title.field.choice"/> <element name="notAvailablePaymentSolutions" type="text" selector="#checkout-payment-method-load>div>div>div.payment-method._active>div.payment-method-title.field.choice"/> - <element name="billingNewAddressForm" type="text" selector=".billing-new-address-form"/> + <element name="billingNewAddressForm" type="text" selector="[data-form='billing-new-address']"/> <element name="placeOrderDisabled" type="button" selector="#checkout-payment-method-load button.disabled"/> <element name="update" type="button" selector=".payment-method-billing-address .action.action-update"/> <element name="guestFirstName" type="input" selector=".billing-address-form input[name*='firstname']"/> @@ -36,8 +36,9 @@ <element name="ProductItemByName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']" parameterized="true" /> <element name="ProductOptionsByProductItemName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true" /> <element name="ProductOptionsActiveByProductItemName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true" /> - <element name="shipToInfomation" type="text" selector="//div[@class='ship-to']//div[@class='shipping-information-content']" /> - <element name="shippingMethodInfomation" type="text" selector="//div[@class='ship-via']//div[@class='shipping-information-content']" /> + <element name="ProductOptionLinkActiveByProductItemName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']//ancestor::div[@class='product-item-details']//div[@class='product options active']//a[text() = '{{var2}}']" parameterized="true" /> + <element name="shipToInformation" type="text" selector="//div[@class='ship-to']//div[@class='shipping-information-content']" /> + <element name="shippingMethodInformation" type="text" selector="//div[@class='ship-via']//div[@class='shipping-information-content']" /> <element name="paymentMethodTitle" type="text" selector=".payment-method-title span" /> <element name="productOptionsByProductItemPrice" type="text" selector="//div[@class='product-item-inner']//div[@class='subtotal']//span[@class='price'][contains(.,'{{var1}}')]//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true"/> <element name="productOptionsActiveByProductItemPrice" type="text" selector="//div[@class='subtotal']//span[@class='price'][contains(.,'{{var1}}')]//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml index ca13af52c1ed5..ad2a43eb90c8c 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutShippingGuestInfoSection"> <element name="email" type="input" selector="#customer-email"/> <element name="firstName" type="input" selector="input[name=firstname]"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml index 552341a531106..ceb4505c79693 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingMethodsSection.xml @@ -7,12 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutShippingMethodsSection"> <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> <element name="firstShippingMethod" type="radio" selector="//*[@id='checkout-shipping-method-load']//input[@class='radio']"/> <element name="shippingMethodRow" type="text" selector=".form.methods-shipping table tbody tr"/> <element name="checkShippingMethodByName" type="radio" selector="//div[@id='checkout-shipping-method-load']//td[contains(., '{{var1}}')]/..//input" parameterized="true"/> <element name="shippingMethodRowByName" type="text" selector="//div[@id='checkout-shipping-method-load']//td[contains(., '{{var1}}')]/.." parameterized="true"/> + <element name="shipHereButton" type="button" selector="//button[contains(@class, 'action-select-shipping-item')]/parent::div/following-sibling::div/button[contains(@class, 'action-select-shipping-item')]"/> + <element name="shippingMethodLoader" type="button" selector="//div[contains(@class, 'checkout-shipping-method')]/following-sibling::div[contains(@class, 'loading-mask')]"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index c20309814d51d..494a365ffd507 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutShippingSection"> <element name="isShippingStep" type="text" selector="//*[@class='opc-progress-bar']/li[contains(@class, '_active') and span[contains(.,'Shipping')]]"/> <element name="shippingTab" type="text" selector="//li[contains(@class,'opc-progress-bar-item')]//*[text()='Shipping']" timeout="30"/> @@ -31,5 +31,6 @@ <element name="next" type="button" selector="button.button.action.continue.primary" timeout="30"/> <element name="firstShippingMethod" type="radio" selector="//*[@id='checkout-shipping-method-load']//input[@class='radio']"/> <element name="defaultShipping" type="button" selector=".billing-address-details"/> + <element name="stateInput" type="input" selector="input[name=region]"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml index 8a55015f9d244..34819f641cbc9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutSuccessMainSection"> <element name="successTitle" type="text" selector=".page-title"/> <element name="success" type="text" selector="div.checkout-success"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml index 271ccec450510..d0ef8d347efb5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutSuccessRegisterSection"> <element name="registerMessage" type="text" selector="#registration p:nth-child(1)"/> <element name="customerEmail" type="text" selector="#registration p:nth-child(2)"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml new file mode 100644 index 0000000000000..89b3a25b45e3c --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + + <section name="ShipmentFormSection"> + <element name="shippingAddress" type="textarea" selector="//*[@class='box box-billing-address']//address"/> + <element name="billingAddress" type="textarea" selector="//*[@class='box box-shipping-address']//address"/> + </section> +</sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontRemoveItemModalSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontRemoveItemModalSection.xml index 4e2a08e94bd9f..e8001af6f0344 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontRemoveItemModalSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StoreFrontRemoveItemModalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StoreFrontRemoveItemModalSection"> <element name="message" type="text" selector="aside.confirm div.modal-content"/> <element name="ok" type="button" selector="aside.confirm .modal-footer .action-primary"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCategoryProductSection.xml index 0edbb21bc6f5d..0427938a02df1 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCategoryProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCategoryProductSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryProductSection"> <element name="ProductAddToCartByNumber" type="button" selector="//main//li[{{var1}}]//button[contains(@class, 'tocart')]" parameterized="true"/> <element name="ProductAddToCartByName" type="button" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//button[contains(@class, 'tocart')]" parameterized="true"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMessagesSection.xml index 4341d99c3fb30..e70ff2b445194 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontMessagesSection"> <!-- @TODO: Use general message selector after MQE-694 is fixed --> <element name="messageProductAddedToCart" type="text" diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml index 0fa5dc8a42341..1c46b8677503b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -7,10 +7,9 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontMinicartSection"> <element name="productCount" type="text" selector="//header//div[contains(@class, 'minicart-wrapper')]//a[contains(@class, 'showcart')]//span[@class='counter-number']"/> - <element name="viewAndEditCart" type="button" selector="//header//div[contains(@class, 'minicart-wrapper')]//a[contains(@class, 'viewcart')]"/> <element name="productLinkByName" type="button" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details']//a[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="productPriceByName" type="text" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details'][.//a[contains(text(), '{{var1}}')]]//span[@class='price']" parameterized="true"/> <element name="productImageByName" type="text" selector="//header//ol[@id='mini-cart']//span[@class='product-image-container']//img[@alt='{{var1}}']" parameterized="true"/> @@ -23,5 +22,8 @@ <element name="viewAndEditCart" type="button" selector=".action.viewcart" timeout="30"/> <element name="miniCartItemsText" type="text" selector=".minicart-items"/> <element name="deleteMiniCartItem" type="button" selector=".action.delete" timeout="30"/> + <element name="miniCartSubtotalField" type="text" selector=".block-minicart .amount span.price"/> + <element name="itemQuantity" type="input" selector="//a[text()='{{productName}}']/../..//input[contains(@class,'cart-item-qty')]" parameterized="true"/> + <element name="itemQuantityUpdate" type="button" selector="//a[text()='{{productName}}']/../..//span[text()='Update']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCompareMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCompareMainSection.xml index 823260be42f2a..200a742e58f14 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCompareMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductCompareMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductCompareMainSection"> <element name="ProductAddToCartByName" type="button" selector="//*[@id='product-comparison']//td[.//strong[@class='product-item-name']/a[contains(text(), '{{var1}}')]]//button[contains(@class, 'tocart')]" parameterized="true"/> </section> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 1ff5d2c874459..95929401620b8 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="AddToCart" type="button" selector="#product-addtocart-button"/> </section> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml new file mode 100644 index 0000000000000..52a69307550c5 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest"> + <annotations> + <features value="Checkout"/> + <stories value="Guest checkout"/> + <title value="Address State Field For UK Customers Remain Option even After Browser Refresh"/> + <description value="Address State Field For UK Customers Remain Option even After Browser Refresh"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93329"/> + <group value="checkout"/> + <skip> + <issueId value="MAGETWO-93726"/> + </skip> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> + <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutFromMinicart" /> + <selectOption stepKey="selectCounty" selector="{{CheckoutShippingSection.country}}" userInput="{{UK_Address.country_id}}"/> + <waitForPageLoad stepKey="waitFormToReload"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitFormToReload1"/> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="enterEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{UK_Address.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{UK_Address.city}}" stepKey="enterCity"/> + <fillField selector="{{CheckoutShippingSection.stateInput}}" userInput="{{UK_Address.province}}" stepKey="enterProvince"/> + <waitForPageLoad stepKey="waitFormToReload2"/> + <see userInput="State/Province" stepKey="StateFieldStillExists"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{UK_Address.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> + + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml new file mode 100644 index 0000000000000..71a0c7f7fbdb3 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AddressStateFieldShouldNotAcceptJustIntegerValuesTest"> + <annotations> + <features value="Checkout"/> + <stories value="MAGETWO-91465"/> + <title value="Guest Checkout"/> + <description value="Address State field should not allow just integer values"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93203"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> + <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="guestGoToCheckoutFromMinicart" /> + <selectOption stepKey="selectCounty" selector="{{CheckoutShippingSection.country}}" userInput="{{UK_Address.country_id}}"/> + <waitForPageLoad stepKey="waitFormToReload"/> + <fillField selector="{{CheckoutShippingSection.stateInput}}" userInput="1" stepKey="enterStateAsIntegerValue"/> + <waitForPageLoad stepKey="waitforFormValidation"/> + <see userInput="First character must be letter." stepKey="seeTheErrorMessageDisplayed"/> + <fillField selector="{{CheckoutShippingSection.stateInput}}" userInput=" 1" stepKey="enterStateAsIntegerValue1"/> + <waitForPageLoad stepKey="waitforFormValidation1"/> + <see userInput="First character must be letter." stepKey="seeTheErrorMessageDisplayed1"/> + <fillField selector="{{CheckoutShippingSection.stateInput}}" userInput="ABC1" stepKey="enterStateAsIntegerValue2"/> + <waitForPageLoad stepKey="waitforFormValidation2"/> + <dontSee userInput="First character must be letter." stepKey="seeTheErrorMessageIsNotDisplayed"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml index d718222283586..a41b1afc74368 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="CheckCheckoutSuccessPageAsRegisterCustomer"> <annotations> <features value="Checkout"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index e386698092aa4..00d80cc2a94d9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <!-- Step 3: User adds products to cart --> <comment userInput="Start of adding products to cart" stepKey="startOfAddingProductsToCart" after="endOfBrowsingCatalog"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 6effeec685106..05a6939941f3e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <!-- Step 3: User adds products to cart --> <comment userInput="Start of adding products to cart" stepKey="startOfAddingProductsToCart" after="endOfBrowsingCatalog"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml new file mode 100644 index 0000000000000..9664ec47420cc --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="IdentityOfDefaultBillingAndShippingAddressTest"> + <annotations> + <features value="Customer"/> + <title value="Checking assignment of default billing address after placing an orde"/> + <description value="In 'Address book' field 'Default Billing Address' should be the same as 'Default Shipping Address'"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94108"/> + <stories value="MAGETWO-62891: New address is not marked as 'Default Billing'"/> + <group value="customer"/> + </annotations> + + <before> + <!--Create product--> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="product" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + + <!--Go to Storefront--> + <amOnPage url="" stepKey="DoToStorefront"/> + + <!-- Fill out form for a new user with address --> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="signUpNewUser"> + <argument name="Customer" value="Simple_US_Customer_NY"/> + </actionGroup> + + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart1"> + <argument name="product" value="$$product$$"/> + </actionGroup> + + <!--Proceed to shipment--> + <amOnPage url="{{CheckoutPage.url}}/" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForShippingSection"/> + + <!--Fill shipment form--> + <actionGroup ref="LoggedInUserCheckoutFillingShippingSectionActionGroup" stepKey="checkoutFillingShippingSection" > + <argument name="customerVar" value="Simple_US_Customer_NY" /> + <argument name="customerAddressVar" value="US_Address_NY" /> + </actionGroup> + + <!--Fill cart data--> + <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyOrderPayment" /> + + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeorder"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage" /> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + <!--Go To My Account--> + <amOnPage stepKey="goToMyAccountPage" url="/customer/account/"/> + + <!--Assert That Shipping And Billing Address are the same--> + <actionGroup ref="AssertThatShippingAndBillingAddressTheSame" stepKey="assertThatShippingAndBillingAddressTheSame"/> + + <after> + <!--Delete created Product--> + <deleteData stepKey="deleteProduct" createDataKey="product"/> + <deleteData stepKey="deleteCategory" createDataKey="category"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml index efa8b4ca75147..1f3d9db5ca524 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="NoErrorCartCheckoutForProductsDeletedFromMiniCartTest"> <annotations> <features value="Checkout"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index c88f6cec4bcdb..6ea8d8a167294 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCustomerCheckoutTest"> <annotations> <features value="Checkout"/> @@ -60,6 +60,7 @@ <amOnPage stepKey="s67" url="{{AdminOrdersPage.url}}"/> <waitForPageLoad stepKey="s75"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFiltersIfPresent"/> <fillField stepKey="s77" selector="{{AdminOrdersGridSection.search}}" userInput="{$s53}" /> <waitForPageLoad stepKey="s78"/> @@ -82,4 +83,114 @@ <click stepKey="s102" selector="{{AdminEditCustomerInformationSection.orders}}"/> <see stepKey="s103" selector="{{AdminEditCustomerOrdersSection.orderGrid}}" userInput="$$simpleuscustomer.firstname$$ $$simpleuscustomer.lastname$$" /> </test> + <test name="StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRates"> + <annotations> + <features value="Checkout"/> + <stories value="Customer checkout"/> + <title value="Customer Checkout with multiple addresses and tax rates"/> + <description value="Should be able to place an order as a customer with multiple addresses and tax rates."/> + <testCaseId value="MAGETWO-93109"/> + <severity value="AVERAGE"/> + <skip> + <issueId value="MQE-1187" /> + </skip> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="simpleproduct1"> + <requiredEntity createDataKey="simplecategory"/> + </createData> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="multiple_address_customer"/> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Go to tax rule page --> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulePage"/> + <waitForPageLoad stepKey="waitForTaxRatePage"/> + <click stepKey="addNewTaxRate" selector="{{AdminGridMainControls.add}}"/> + <fillField stepKey="fillRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="SampleRule"/> + + <!-- Add NY and CA tax rules --> + <actionGroup ref="addNewTaxRateNoZip" stepKey="addNYTaxRate"> + <argument name="taxCode" value="SimpleTaxNY"/> + </actionGroup> + + <actionGroup ref="addNewTaxRateNoZip" stepKey="addCATaxRate"> + <argument name="taxCode" value="SimpleTaxCA"/> + </actionGroup> + + <click stepKey="clickSave" selector="{{AdminStoresMainActionsSection.saveButton}}"/> + </before> + <after> + <!-- Go to the tax rule page and delete the row we created--> + <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulesPage"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> + <argument name="name" value="SampleRule"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <!-- Go to the tax rate page --> + <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRatesPage"/> + <waitForPageLoad stepKey="waitForRatesPage"/> + <!-- Delete the two tax rates that were created --> + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteNYRate"> + <argument name="name" value="{{SimpleTaxNY.state}}-{{SimpleTaxNY.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + + <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteCARate"> + <argument name="name" value="{{SimpleTaxCA.state}}-{{SimpleTaxCA.rate}}"/> + <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> + </actionGroup> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + <deleteData createDataKey="simpleproduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> + <deleteData createDataKey="multiple_address_customer" stepKey="deleteCustomer"/> + </after> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$multiple_address_customer$$" /> + </actionGroup> + + <amOnPage url="{{StorefrontCategoryPage.url($$simplecategory.name$$)}}" stepKey="onCategoryPage1"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad1"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct1"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart1"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded1"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$simpleproduct1.name$$ to your shopping cart." stepKey="seeAddedToCartMessage1"/> + <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity1"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1" /> + + <click stepKey="selectFirstShippingMethod1" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> + <waitForElement stepKey="waitForShippingMethodSelect1" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> + <click stepKey="clickNextOnShippingMethodLoad1" selector="{{CheckoutShippingMethodsSection.next}}" /> + <waitForPageLoad stepKey="waitForPaymentLoad1"/> + <waitForElement stepKey="waitForPlaceOrderButton1" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> + <see stepKey="seeBillingAddressIsCorrect1" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{US_Address_NY.street[0]}}" /> + <click stepKey="clickPlaceOrderButton1" selector="{{CheckoutPaymentSection.placeOrder}}" /> + <waitForPageLoad stepKey="waitForOrderSuccessPage1"/> + <see stepKey="seeSuccessMessage1" selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" /> + + <amOnPage url="{{StorefrontCategoryPage.url($$simplecategory.name$$)}}" stepKey="onCategoryPage2"/> + <waitForPageLoad stepKey="waitForCatalogPageLoad2"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct2"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart2"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded2"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$simpleproduct1.name$$ to your shopping cart." stepKey="seeAddedToCartMessage2"/> + <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity2"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2" /> + + <click stepKey="changeShippingAddress" selector="{{CheckoutShippingMethodsSection.shipHereButton}}"/> + <waitForElementNotVisible stepKey="waitForShippingMethodLoaderNotVisible" selector="{{CheckoutShippingMethodsSection.shippingMethodLoader}}" time="30"/> + <click stepKey="selectFirstShippingMethod2" selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}"/> + <waitForElement stepKey="waitForShippingMethodSelect2" selector="{{CheckoutShippingMethodsSection.next}}" time="30"/> + <click stepKey="clickNextOnShippingMethodLoad2" selector="{{CheckoutShippingMethodsSection.next}}" /> + <waitForPageLoad stepKey="waitForPaymentLoad2"/> + <waitForElement stepKey="waitForPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" time="30" /> + <see stepKey="seeBillingAddressIsCorrect2" selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{UK_Not_Default_Address.street[0]}}" /> + <click stepKey="clickPlaceOrderButton2" selector="{{CheckoutPaymentSection.placeOrder}}" /> + <waitForPageLoad stepKey="waitForOrderSuccessPage2"/> + <see stepKey="seeSuccessMessage2" selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" /> + </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index 9d88e42447cb6..a7b82d54afb30 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontGuestCheckoutTest"> <annotations> <features value="Checkout"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/composer.json b/app/code/Magento/Checkout/Test/Mftf/composer.json deleted file mode 100644 index 32c1a2c6bcd3f..0000000000000 --- a/app/code/Magento/Checkout/Test/Mftf/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "magento/functional-test-module-checkout", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php index 9c9c5fd33bd07..40154563774d5 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/Item/RendererTest.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Product; use Magento\Checkout\Block\Cart\Item\Renderer; use Magento\Quote\Model\Quote\Item; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -18,17 +19,22 @@ class RendererTest extends \PHPUnit\Framework\TestCase /** * @var Renderer */ - protected $_renderer; + private $renderer; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $layout; + private $layout; /** * @var \Magento\Catalog\Block\Product\ImageBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $imageBuilder; + private $imageBuilder; + + /** + * @var ItemResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $itemResolver; protected function setUp() { @@ -41,17 +47,22 @@ protected function setUp() ->getMock(); $context->expects($this->once()) ->method('getLayout') - ->will($this->returnValue($this->layout)); + ->willReturn($this->layout); $this->imageBuilder = $this->getMockBuilder(\Magento\Catalog\Block\Product\ImageBuilder::class) ->disableOriginalConstructor() ->getMock(); - $this->_renderer = $objectManagerHelper->getObject( + $this->itemResolver = $this->createMock( + ItemResolverInterface::class + ); + + $this->renderer = $objectManagerHelper->getObject( \Magento\Checkout\Block\Cart\Item\Renderer::class, [ 'context' => $context, 'imageBuilder' => $this->imageBuilder, + 'itemResolver' => $this->itemResolver, ] ); } @@ -59,7 +70,7 @@ protected function setUp() public function testGetProductForThumbnail() { $product = $this->_initProduct(); - $productForThumbnail = $this->_renderer->getProductForThumbnail(); + $productForThumbnail = $this->renderer->getProductForThumbnail(); $this->assertEquals($product->getName(), $productForThumbnail->getName(), 'Invalid product was returned.'); } @@ -75,13 +86,18 @@ protected function _initProduct() Product::class, ['getName', '__wakeup', 'getIdentities'] ); - $product->expects($this->any())->method('getName')->will($this->returnValue('Parent Product')); + $product->expects($this->any())->method('getName')->willReturn('Parent Product'); /** @var Item|\PHPUnit_Framework_MockObject_MockObject $item */ $item = $this->createMock(\Magento\Quote\Model\Quote\Item::class); - $item->expects($this->any())->method('getProduct')->will($this->returnValue($product)); + $item->expects($this->any())->method('getProduct')->willReturn($product); + + $this->itemResolver->expects($this->any()) + ->method('getFinalProduct') + ->with($item) + ->willReturn($product); - $this->_renderer->setItem($item); + $this->renderer->setItem($item); return $product; } @@ -91,14 +107,14 @@ public function testGetIdentities() $identities = [1 => 1, 2 => 2, 3 => 3]; $product->expects($this->exactly(2)) ->method('getIdentities') - ->will($this->returnValue($identities)); + ->willReturn($identities); - $this->assertEquals($product->getIdentities(), $this->_renderer->getIdentities()); + $this->assertEquals($product->getIdentities(), $this->renderer->getIdentities()); } public function testGetIdentitiesFromEmptyItem() { - $this->assertEmpty($this->_renderer->getIdentities()); + $this->assertEmpty($this->renderer->getIdentities()); } /** @@ -119,7 +135,7 @@ public function testGetProductPriceHtml() $this->layout->expects($this->atLeastOnce()) ->method('getBlock') ->with('product.price.render.default') - ->will($this->returnValue($priceRender)); + ->willReturn($priceRender); $priceRender->expects($this->once()) ->method('render') @@ -131,9 +147,9 @@ public function testGetProductPriceHtml() 'display_minimal_price' => true, 'zone' => \Magento\Framework\Pricing\Render::ZONE_ITEM_LIST ] - )->will($this->returnValue($priceHtml)); + )->willReturn($priceHtml); - $this->assertEquals($priceHtml, $this->_renderer->getProductPriceHtml($product)); + $this->assertEquals($priceHtml, $this->renderer->getProductPriceHtml($product)); } public function testGetActions() @@ -150,7 +166,7 @@ public function testGetActions() $this->layout->expects($this->once()) ->method('getChildName') - ->with($this->_renderer->getNameInLayout(), 'actions') + ->with($this->renderer->getNameInLayout(), 'actions') ->willReturn($blockNameInLayout); $this->layout->expects($this->once()) ->method('getBlock') @@ -171,14 +187,14 @@ public function testGetActions() ->method('toHtml') ->willReturn($blockHtml); - $this->assertEquals($blockHtml, $this->_renderer->getActions($itemMock)); + $this->assertEquals($blockHtml, $this->renderer->getActions($itemMock)); } public function testGetActionsWithNoBlock() { $this->layout->expects($this->once()) ->method('getChildName') - ->with($this->_renderer->getNameInLayout(), 'actions') + ->with($this->renderer->getNameInLayout(), 'actions') ->willReturn(false); /** @@ -188,7 +204,7 @@ public function testGetActionsWithNoBlock() ->disableOriginalConstructor() ->getMock(); - $this->assertEquals('', $this->_renderer->getActions($itemMock)); + $this->assertEquals('', $this->renderer->getActions($itemMock)); } public function testGetImage() @@ -198,14 +214,14 @@ public function testGetImage() $product = $this->createMock(Product::class); $imageMock = $this->createMock(Image::class); - $this->imageBuilder->expects(self::once()) + $this->imageBuilder->expects($this->once()) ->method('create') ->with($product, $imageId, $attributes) ->willReturn($imageMock); - static::assertInstanceOf( + $this->assertInstanceOf( Image::class, - $this->_renderer->getImage($product, $imageId, $attributes) + $this->renderer->getImage($product, $imageId, $attributes) ); } } diff --git a/app/code/Magento/Checkout/Test/Unit/Controller/Cart/AddTest.php b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/AddTest.php new file mode 100644 index 0000000000000..7c0e542dd6705 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Controller/Cart/AddTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Test\Unit\Controller\Cart; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class AddTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var \Magento\Framework\Data\Form\FormKey\Validator|\PHPUnit_Framework_MockObject_MockObject + */ + private $formKeyValidator; + + /** + * @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultRedirectFactory; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $request; + + /** + * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $messageManager; + + /** + * @var \Magento\Checkout\Controller\Cart\Add|\PHPUnit_Framework_MockObject_MockObject + */ + private $cartAdd; + + /** + * Init mocks for tests. + * + * @return void + */ + public function setUp() + { + $this->formKeyValidator = $this->getMockBuilder(\Magento\Framework\Data\Form\FormKey\Validator::class) + ->disableOriginalConstructor()->getMock(); + $this->resultRedirectFactory = + $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) + ->disableOriginalConstructor()->getMock(); + $this->request = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->disableOriginalConstructor()->getmock(); + $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + ->disableOriginalConstructor()->getMock(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->cartAdd = $this->objectManagerHelper->getObject( + \Magento\Checkout\Controller\Cart\Add::class, + [ + '_formKeyValidator' => $this->formKeyValidator, + 'resultRedirectFactory' => $this->resultRedirectFactory, + '_request' => $this->request, + 'messageManager' => $this->messageManager + ] + ); + } + + /** + * Test for method execute. + * + * @return void + */ + public function testExecute() + { + $redirect = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + $path = '*/*/'; + + $this->formKeyValidator->expects($this->once())->method('validate')->with($this->request)->willReturn(false); + $this->messageManager->expects($this->once())->method('addErrorMessage'); + $this->resultRedirectFactory->expects($this->once())->method('create')->willReturn($redirect); + $redirect->expects($this->once())->method('setPath')->with($path)->willReturnSelf(); + $this->assertEquals($redirect, $this->cartAdd->execute()); + } +} diff --git a/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php b/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php index 8a7c2e951dd72..9a408f1ecd1c8 100644 --- a/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php +++ b/app/code/Magento/Checkout/Test/Unit/CustomerData/DefaultItemTest.php @@ -5,12 +5,14 @@ */ namespace Magento\Checkout\Test\Unit\CustomerData; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; + class DefaultItemTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Checkout\CustomerData\DefaultItem */ - protected $model; + private $model; /** * @var \Magento\Catalog\Helper\Image @@ -22,6 +24,11 @@ class DefaultItemTest extends \PHPUnit\Framework\TestCase */ private $configurationPool; + /** + * @var ItemResolverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $itemResolver; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -35,12 +42,14 @@ protected function setUp() $checkoutHelper = $this->getMockBuilder(\Magento\Checkout\Helper\Data::class) ->setMethods(['formatPrice'])->disableOriginalConstructor()->getMock(); $checkoutHelper->expects($this->any())->method('formatPrice')->willReturn(5); + $this->itemResolver = $this->createMock(ItemResolverInterface::class); $this->model = $objectManager->getObject( \Magento\Checkout\CustomerData\DefaultItem::class, [ 'imageHelper' => $this->imageHelper, 'configurationPool' => $this->configurationPool, - 'checkoutHelper' => $checkoutHelper + 'checkoutHelper' => $checkoutHelper, + 'itemResolver' => $this->itemResolver, ] ); } @@ -73,6 +82,11 @@ public function testGetItemData() $this->imageHelper->expects($this->any())->method('getHeight')->willReturn(100); $this->configurationPool->expects($this->any())->method('getByProductType')->willReturn($product); + $this->itemResolver->expects($this->any()) + ->method('getFinalProduct') + ->with($item) + ->will($this->returnValue($product)); + $itemData = $this->model->getItemData($item); $this->assertArrayHasKey('options', $itemData); $this->assertArrayHasKey('qty', $itemData); diff --git a/app/code/Magento/Checkout/Test/Unit/Model/Cart/CollectQuoteTest.php b/app/code/Magento/Checkout/Test/Unit/Model/Cart/CollectQuoteTest.php new file mode 100644 index 0000000000000..14410578b12e4 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/CollectQuoteTest.php @@ -0,0 +1,178 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Test\Unit\Model\Cart; + +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\RegionInterface; +use Magento\Checkout\Model\Cart\CollectQuote; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\EstimateAddressInterface; +use Magento\Quote\Api\Data\EstimateAddressInterfaceFactory; +use Magento\Quote\Api\ShippingMethodManagementInterface; +use Magento\Quote\Model\Quote; +use PHPUnit\Framework\TestCase; + +/** + * Class CollectQuoteTest + */ +class CollectQuoteTest extends TestCase +{ + /** + * @var CollectQuote + */ + private $model; + + /** + * @var CustomerSession|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerSessionMock; + + /** + * @var CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerRepositoryMock; + + /** + * @var AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressRepositoryMock; + + /** + * @var EstimateAddressInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $estimateAddressFactoryMock; + + /** + * @var EstimateAddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $estimateAddressMock; + + /** + * @var ShippingMethodManagementInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $shippingMethodManagerMock; + + /** + * @var CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteRepositoryMock; + + /** + * @var Quote|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteMock; + + /** + * @var CustomerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerMock; + + /** + * @var AddressInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressMock; + + /** + * Set up + */ + protected function setUp() + { + $this->customerSessionMock = $this->createMock(CustomerSession::class); + $this->customerRepositoryMock = $this->getMockForAbstractClass( + CustomerRepositoryInterface::class, + [], + '', + false, + true, + true, + ['getById'] + ); + $this->addressRepositoryMock = $this->createMock(AddressRepositoryInterface::class); + $this->estimateAddressMock = $this->createMock(EstimateAddressInterface::class); + $this->estimateAddressFactoryMock = + $this->createPartialMock(EstimateAddressInterfaceFactory::class, ['create']); + $this->shippingMethodManagerMock = $this->createMock(ShippingMethodManagementInterface::class); + $this->quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class); + $this->quoteMock = $this->createMock(Quote::class); + $this->customerMock = $this->createMock(CustomerInterface::class); + $this->addressMock = $this->createMock(AddressInterface::class); + + $this->model = new CollectQuote( + $this->customerSessionMock, + $this->customerRepositoryMock, + $this->addressRepositoryMock, + $this->estimateAddressFactoryMock, + $this->shippingMethodManagerMock, + $this->quoteRepositoryMock + ); + } + + /** + * Test collect method + */ + public function testCollect() + { + $customerId = 1; + $defaultAddressId = 999; + $countryId = 'USA'; + $regionId = 'CA'; + $regionMock = $this->createMock(RegionInterface::class); + + $this->customerSessionMock->expects(self::once()) + ->method('isLoggedIn') + ->willReturn(true); + $this->customerSessionMock->expects(self::once()) + ->method('getCustomerId') + ->willReturn($customerId); + $this->customerRepositoryMock->expects(self::once()) + ->method('getById') + ->willReturn($this->customerMock); + $this->customerMock->expects(self::once()) + ->method('getDefaultShipping') + ->willReturn($defaultAddressId); + $this->addressMock->expects(self::once()) + ->method('getCountryId') + ->willReturn($countryId); + $regionMock->expects(self::once()) + ->method('getRegion') + ->willReturn($regionId); + $this->addressMock->expects(self::once()) + ->method('getRegion') + ->willReturn($regionMock); + $this->addressRepositoryMock->expects(self::once()) + ->method('getById') + ->with($defaultAddressId) + ->willReturn($this->addressMock); + $this->estimateAddressFactoryMock->expects(self::once()) + ->method('create') + ->willReturn($this->estimateAddressMock); + $this->quoteRepositoryMock->expects(self::once()) + ->method('save') + ->with($this->quoteMock); + + $this->model->collect($this->quoteMock); + } + + /** + * Test with a not logged in customer + */ + public function testCollectWhenCustomerIsNotLoggedIn() + { + $this->customerSessionMock->expects(self::once()) + ->method('isLoggedIn') + ->willReturn(false); + $this->customerRepositoryMock->expects(self::never()) + ->method('getById'); + + $this->model->collect($this->quoteMock); + } +} diff --git a/app/code/Magento/Checkout/Test/Unit/Model/Cart/ImageProviderTest.php b/app/code/Magento/Checkout/Test/Unit/Model/Cart/ImageProviderTest.php index 5330d93b46f6a..993a01d922c1c 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/Cart/ImageProviderTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/ImageProviderTest.php @@ -11,25 +11,34 @@ class ImageProviderTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Checkout\Model\Cart\ImageProvider */ - public $model; + private $model; /** * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Quote\Api\CartItemRepositoryInterface */ - protected $itemRepositoryMock; + private $itemRepositoryMock; /** * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Checkout\CustomerData\ItemPoolInterface */ - protected $itemPoolMock; + private $itemPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Checkout\CustomerData\DefaultItem + */ + private $customerItem; protected function setUp() { $this->itemRepositoryMock = $this->createMock(\Magento\Quote\Api\CartItemRepositoryInterface::class); $this->itemPoolMock = $this->createMock(\Magento\Checkout\CustomerData\ItemPoolInterface::class); + $this->customerItem = $this->getMockBuilder(\Magento\Checkout\CustomerData\DefaultItem::class) + ->disableOriginalConstructor() + ->getMock(); $this->model = new \Magento\Checkout\Model\Cart\ImageProvider( $this->itemRepositoryMock, - $this->itemPoolMock + $this->itemPoolMock, + $this->customerItem ); } @@ -44,7 +53,7 @@ public function testGetImages() $expectedResult = [$itemId => $itemData['product_image']]; $this->itemRepositoryMock->expects($this->once())->method('getList')->with($cartId)->willReturn([$itemMock]); - $this->itemPoolMock->expects($this->once())->method('getItemData')->with($itemMock)->willReturn($itemData); + $this->customerItem->expects($this->once())->method('getItemData')->with($itemMock)->willReturn($itemData); $this->assertEquals($expectedResult, $this->model->getImages($cartId)); } diff --git a/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestQuantityProcessorTest.php b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestQuantityProcessorTest.php new file mode 100644 index 0000000000000..daabb080b1c9a --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestQuantityProcessorTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Test\Unit\Model\Cart; + +use Magento\Checkout\Model\Cart\RequestQuantityProcessor; +use Magento\Framework\Locale\ResolverInterface; +use \PHPUnit_Framework_MockObject_MockObject as MockObject; + +class RequestQuantityProcessorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ResolverInterface | MockObject + */ + private $localeResolver; + + /** + * @var RequestQuantityProcessor + */ + private $requestProcessor; + + protected function setUp() + { + $this->localeResolver = $this->getMockBuilder(ResolverInterface::class) + ->getMockForAbstractClass(); + + $this->localeResolver->method('getLocale') + ->willReturn('en_US'); + + $this->requestProcessor = new RequestQuantityProcessor( + $this->localeResolver + ); + } + + /** + * Test of cart data processing. + * + * @param array $cartData + * @param array $expected + * @dataProvider cartDataProvider + */ + public function testProcess($cartData, $expected) + { + $this->assertEquals($this->requestProcessor->process($cartData), $expected); + } + + public function cartDataProvider() + { + return [ + 'empty_array' => [ + 'cartData' => [], + 'expected' => [], + ], + 'strings_array' => [ + 'cartData' => [ + ['qty' => ' 10 '], + ['qty' => ' 0.5 '] + ], + 'expected' => [ + ['qty' => 10], + ['qty' => 0.5] + ], + ], + 'integer_array' => [ + 'cartData' => [ + ['qty' => 1], + ['qty' => 0.002] + ], + 'expected' => [ + ['qty' => 1], + ['qty' => 0.002] + ], + ], + 'array_of arrays' => [ + 'cartData' => [ + ['qty' => [1, 2 ,3]], + ], + 'expected' => [ + ['qty' => [1, 2, 3]], + ], + ], + ]; + } +} diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index 6947e1162600a..11e3ba5f3ed9a 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -41,6 +41,10 @@ <field id="number_items_to_display_pager" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Number of Items to Display Pager</label> </field> + <field id="crosssell_enabled" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Show Cross-sell Items in the Shopping Cart</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> </group> <group id="cart_link" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0"> <label>My Cart Link</label> diff --git a/app/code/Magento/Checkout/etc/config.xml b/app/code/Magento/Checkout/etc/config.xml index 3c24c38ecf85b..e1ba4381f2230 100644 --- a/app/code/Magento/Checkout/etc/config.xml +++ b/app/code/Magento/Checkout/etc/config.xml @@ -17,6 +17,7 @@ <delete_quote_after>30</delete_quote_after> <redirect_to_cart>0</redirect_to_cart> <number_items_to_display_pager>20</number_items_to_display_pager> + <crosssell_enabled>1</crosssell_enabled> </cart> <cart_link> <use_qty>1</use_qty> diff --git a/app/code/Magento/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml index 4ebd594a28562..71dfd12bb4779 100644 --- a/app/code/Magento/Checkout/etc/di.xml +++ b/app/code/Magento/Checkout/etc/di.xml @@ -49,4 +49,7 @@ </argument> </arguments> </type> + <type name="Magento\Quote\Model\Quote"> + <plugin name="clear_addresses_after_product_delete" type="Magento\Checkout\Plugin\Model\Quote\ResetQuoteAddresses"/> + </type> </config> diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index 889689e6c0d16..f2aced01b3942 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -39,7 +39,7 @@ </type> <preference for="Magento\Checkout\CustomerData\ItemPoolInterface" type="Magento\Checkout\CustomerData\ItemPool"/> - <type name="Magento\Checkout\CustomerData\ItemPoolInterface"> + <type name="Magento\Checkout\CustomerData\ItemPool"> <arguments> <argument name="defaultItemId" xsi:type="string">Magento\Checkout\CustomerData\DefaultItem</argument> </arguments> diff --git a/app/code/Magento/Checkout/i18n/en_US.csv b/app/code/Magento/Checkout/i18n/en_US.csv index 2dcb611c1fe60..a6ea2c13579a7 100644 --- a/app/code/Magento/Checkout/i18n/en_US.csv +++ b/app/code/Magento/Checkout/i18n/en_US.csv @@ -181,3 +181,4 @@ Payment,Payment "Item in Cart","Item in Cart" "Items in Cart","Items in Cart" "Close","Close" +"Show Cross-sell Items in the Shopping Cart","Show Cross-sell Items in the Shopping Cart" diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml index ff4c6dbd35ff2..69d2523d88dfb 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_cart_index.xml @@ -186,7 +186,7 @@ </block> <container name="checkout.cart.widget" as="checkout_cart_widget" label="Shopping Cart Items After"/> </container> - <block class="Magento\Checkout\Block\Cart\Crosssell" name="checkout.cart.crosssell" template="Magento_Catalog::product/list/items.phtml" after="-"> + <block class="Magento\Checkout\Block\Cart\Crosssell" name="checkout.cart.crosssell" template="Magento_Catalog::product/list/items.phtml" after="-" ifconfig="checkout/cart/crosssell_enabled"> <arguments> <argument name="type" xsi:type="string">crosssell</argument> </arguments> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml index 4940cd7f26747..71b1392d5391f 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml @@ -13,7 +13,9 @@ <form action="<?= /* @escapeNotVerified */ $block->getUrl('checkout/cart/updatePost') ?>" method="post" id="form-validate" - data-mage-init='{"validation":{}}' + data-mage-init='{"Magento_Checkout/js/action/update-shopping-cart": + {"validationURL" : "/checkout/cart/updateItemQty"} + }' class="form form-cart"> <?= $block->getBlockHtml('formkey') ?> <div class="cart table-wrapper<?= $mergedCells == 2 ? ' detailed' : '' ?>"> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml index 5530f7661bb1b..d329e2e8c1770 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/methods.phtml @@ -14,7 +14,8 @@ <?php $methods = $block->getMethods('methods') ?: $block->getMethods('top_methods') ?> <ul class="checkout methods items checkout-methods-items"> <?php foreach ($methods as $method): ?> - <?php if ($methodHtml = $block->getMethodHtml($method)): ?> + <?php $methodHtml = $block->getMethodHtml($method); ?> + <?php if (trim($methodHtml) !== ''): ?> <li class="item"><?= /* @escapeNotVerified */ $methodHtml ?></li> <?php endif; ?> <?php endforeach; ?> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js new file mode 100644 index 0000000000000..ce1527b3d72d6 --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js @@ -0,0 +1,127 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/modal/alert', + 'jquery', + 'jquery/ui', + 'mage/validation' +], function (alert, $) { + 'use strict'; + + $.widget('mage.updateShoppingCart', { + options: { + validationURL: '', + eventName: 'updateCartItemQty' + }, + + /** @inheritdoc */ + _create: function () { + this._on(this.element, { + 'submit': this.onSubmit + }); + }, + + /** + * Prevents default submit action and calls form validator. + * + * @param {Event} event + * @return {Boolean} + */ + onSubmit: function (event) { + if (!this.options.validationURL) { + return true; + } + + if (this.isValid()) { + event.preventDefault(); + this.validateItems(this.options.validationURL, this.element.serialize()); + } + + return false; + }, + + /** + * Validates requested form. + * + * @return {Boolean} + */ + isValid: function () { + return this.element.validation() && this.element.validation('isValid'); + }, + + /** + * Validates updated shopping cart data. + * + * @param {String} url - request url + * @param {Object} data - post data for ajax call + */ + validateItems: function (url, data) { + $.extend(data, { + 'form_key': $.mage.cookies.get('form_key') + }); + + $.ajax({ + url: url, + data: data, + type: 'post', + dataType: 'json', + context: this, + + /** @inheritdoc */ + beforeSend: function () { + $(document.body).trigger('processStart'); + }, + + /** @inheritdoc */ + complete: function () { + $(document.body).trigger('processStop'); + } + }) + .done(function (response) { + if (response.success) { + this.onSuccess(); + } else { + this.onError(response); + } + }) + .fail(function () { + this.submitForm(); + }); + }, + + /** + * Form validation succeed. + */ + onSuccess: function () { + $(document).trigger('ajax:' + this.options.eventName); + this.submitForm(); + }, + + /** + * Form validation failed. + */ + onError: function (response) { + if (response['error_message']) { + alert({ + content: response['error_message'] + }); + } else { + this.submitForm(); + } + }, + + /** + * Real submit of validated form. + */ + submitForm: function () { + this.element + .off('submit', this.onSubmit) + .submit(); + } + }); + + return $.mage.updateShoppingCart; +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js index e269462047748..0e94232786c65 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/totals-processor/default.js @@ -38,7 +38,7 @@ define([ payload.addressInformation['shipping_carrier_code'] = quote.shippingMethod()['carrier_code']; } - storage.post( + return storage.post( serviceUrl, JSON.stringify(payload), false ).done(function (result) { var data = { @@ -96,7 +96,7 @@ define([ ) { quote.setTotals(cartCache.get('totals')); } else { - loadFromServer(address); + return loadFromServer(address); } } }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js index 3bc5911946fda..a880bd423ab2e 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js @@ -17,17 +17,23 @@ define([ */ return function (addressData) { var identifier = Date.now(), + countryId = addressData['country_id'] || addressData.countryId || window.checkoutConfig.defaultCountryId, regionId; if (addressData.region && addressData.region['region_id']) { regionId = addressData.region['region_id']; - } else if (addressData['country_id'] && addressData['country_id'] == window.checkoutConfig.defaultCountryId) { //eslint-disable-line + } else if ( + /* eslint-disable */ + addressData['country_id'] && addressData['country_id'] == window.checkoutConfig.defaultCountryId || + !addressData['country_id'] && countryId == window.checkoutConfig.defaultCountryId + /* eslint-enable */ + ) { regionId = window.checkoutConfig.defaultRegionId || undefined; } return { email: addressData.email, - countryId: addressData['country_id'] || addressData.countryId || window.checkoutConfig.defaultCountryId, + countryId: countryId, regionId: regionId || addressData.regionId, regionCode: addressData.region ? addressData.region['region_code'] : null, region: addressData.region ? addressData.region.region : null, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js index dacebd75c3c68..cf2a59cdba427 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/region-updater.js @@ -188,6 +188,8 @@ define([ if (!this.options.optionalRegionAllowed) { //eslint-disable-line max-depth regionList.attr('disabled', 'disabled'); + } else { + regionList.removeAttr('disabled'); } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 3fb8743e951c8..3b5168453e11a 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -9,11 +9,12 @@ define([ 'Magento_Customer/js/customer-data', 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/modal/confirm', + 'underscore', 'jquery/ui', 'mage/decorate', 'mage/collapsible', 'mage/cookies' -], function ($, authenticationPopup, customerData, alert, confirm) { +], function ($, authenticationPopup, customerData, alert, confirm, _) { 'use strict'; $.widget('mage.sidebar', { @@ -60,13 +61,15 @@ define([ }; events['click ' + this.options.button.checkout] = $.proxy(function () { var cart = customerData.get('cart'), - customer = customerData.get('customer'); + customer = customerData.get('customer'), + element = $(this.options.button.checkout); if (!customer().firstname && cart().isGuestCheckoutAllowed === false) { // set URL for redirect on successful login/registration. It's postprocessed on backend. $.cookie('login_redirect', this.options.url.checkout); if (this.options.url.isRedirectRequired) { + element.prop('disabled', true); location.href = this.options.url.loginUrl; } else { authenticationPopup.showModal(); @@ -74,6 +77,7 @@ define([ return false; } + element.prop('disabled', true); location.href = this.options.url.checkout; }, this); @@ -219,8 +223,12 @@ define([ * @param {HTMLElement} elem */ _updateItemQtyAfter: function (elem) { + var productData = this._getProductById(Number(elem.data('cart-item'))); + + if (!_.isUndefined(productData)) { + $(document).trigger('ajax:updateCartItemQty', productData['product_sku']); + } this._hideItemButton(elem); - $(document).trigger('ajax:updateItemQty'); }, /** @@ -242,11 +250,24 @@ define([ * @private */ _removeItemAfter: function (elem) { - var productData = customerData.get('cart')().items.find(function (item) { - return Number(elem.data('cart-item')) === Number(item['item_id']); - }); + var productData = this._getProductById(Number(elem.data('cart-item'))); + + if (!_.isUndefined(productData)) { + $(document).trigger('ajax:removeFromCart', productData['product_sku']); + } + }, - $(document).trigger('ajax:removeFromCart', productData['product_sku']); + /** + * Retrieves product data by Id. + * + * @param {Number} productId - product Id + * @returns {Object|undefined} + * @private + */ + _getProductById: function (productId) { + return _.find(customerData.get('cart')().items, function (item) { + return productId === Number(item['item_id']); + }); }, /** diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js b/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js index 9af5201c267e8..0e2fc6bbfc8fe 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/configure/product-customer-data.js @@ -1,8 +1,9 @@ require([ 'jquery', 'Magento_Customer/js/customer-data', + 'underscore', 'domReady!' -], function ($, customerData) { +], function ($, customerData, _) { 'use strict'; var selectors = { @@ -41,7 +42,7 @@ require([ if (!(data && data.items && data.items.length && productId)) { return; } - product = data.items.find(function (item) { + product = _.find(data.items, function (item) { if (item['item_id'] === itemId) { return item['product_id'] === productId || item['item_id'] === productId; diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html index 086cca814bec1..54fe9a1f59394 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/form.html @@ -9,15 +9,14 @@ <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> <form data-bind="attr: {'data-hasrequired': $t('* Required Fields')}"> - <fieldset data-bind="attr: { id:'billing-new-address-form-'+index, value:index}" - class="billing-new-address-form fieldset address"> + <fieldset class="fieldset address" data-form="billing-new-address"> <!-- ko foreach: getRegion('additional-fieldsets') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> <!-- ko if: (isCustomerLoggedIn && customerHasAddresses) --> <div class="choice field"> - <input type="checkbox" class="checkbox" id="billing-save-in-address-book" data-bind="checked: saveInAddressBook" /> - <label class="label" for="billing-save-in-address-book"> + <input type="checkbox" class="checkbox" data-bind="checked: saveInAddressBook, attr: {id: 'billing-save-in-address-book-' + getCode($parent)}" /> + <label class="label" data-bind="attr: {for: 'billing-save-in-address-book-' + getCode($parent)}" > <span data-bind="i18n: 'Save in address book'"></span> </label> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 41d442a76d510..357b0e550af0f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -45,7 +45,7 @@ <span data-bind="html: option.value.join('<br>')"></span> <!-- /ko --> <!-- ko ifnot: Array.isArray(option.value) --> - <span data-bind="text: option.value"></span> + <span data-bind="html: option.value"></span> <!-- /ko --> </dd> <!-- /ko --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html index 94f94ec878151..dd59bd78416c6 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html @@ -12,7 +12,7 @@ <div class="product-item-inner"> <div class="product-item-name-block"> - <strong class="product-item-name" data-bind="text: $parent.name"></strong> + <strong class="product-item-name" data-bind="html: $parent.name"></strong> <div class="details-qty"> <span class="label"><!-- ko i18n: 'Qty' --><!-- /ko --></span> <span class="value" data-bind="text: $parent.qty"></span> @@ -35,7 +35,7 @@ <dd class="values" data-bind="html: full_view"></dd> <!-- /ko --> <!-- ko ifnot: ($data.full_view)--> - <dd class="values" data-bind="text: value"></dd> + <dd class="values" data-bind="html: value"></dd> <!-- /ko --> <!-- /ko --> </dl> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/thumbnail.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/thumbnail.html index 981541e7251e7..eb218bbee9941 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/thumbnail.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details/thumbnail.html @@ -8,6 +8,6 @@ data-bind="attr: {'style': 'height: ' + getHeight($parents[1]) + 'px; width: ' + getWidth($parents[1]) + 'px;' }"> <span class="product-image-wrapper"> <img - data-bind="attr: {'src': getSrc($parents[1]), 'width': getWidth($parents[1]), 'height': getHeight($parents[1]), 'alt': getAlt($parents[1]) }"/> + data-bind="attr: {'src': getSrc($parents[1]), 'width': getWidth($parents[1]), 'height': getHeight($parents[1]), 'alt': getAlt($parents[1]), 'title': getAlt($parents[1]) }"/> </span> </span> diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement.php index 13130a4491eb2..aa6f461fc5ee2 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement.php @@ -5,7 +5,11 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml; -abstract class Agreement extends \Magento\Backend\App\Action +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Registry; + +abstract class Agreement extends Action { /** * Authorization level of a basic admin session @@ -22,12 +26,14 @@ abstract class Agreement extends \Magento\Backend\App\Action protected $_coreRegistry = null; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry + * @param Context $context + * @param Registry $coreRegistry * @codeCoverageIgnore */ - public function __construct(\Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry) - { + public function __construct( + Context $context, + Registry $coreRegistry + ) { $this->_coreRegistry = $coreRegistry; parent::__construct($context); } diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php index 65aca6205caa4..f7b178df99624 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Delete.php @@ -6,27 +6,53 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; -class Delete extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement +use Magento\CheckoutAgreements\Api\CheckoutAgreementsRepositoryInterface; +use Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Registry; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; + +class Delete extends Agreement { + /** + * @var CheckoutAgreementsRepositoryInterface + */ + private $agreementRepository; + + /** + * @param Context $context + * @param Registry $coreRegistry + * @param CheckoutAgreementsRepositoryInterface $agreementRepository + */ + public function __construct( + Context $context, + Registry $coreRegistry, + CheckoutAgreementsRepositoryInterface $agreementRepository = null + ) { + $this->agreementRepository = $agreementRepository ?: + ObjectManager::getInstance()->get(CheckoutAgreementsRepositoryInterface::class); + parent::__construct($context, $coreRegistry); + } /** * @return void */ public function execute() { $id = (int)$this->getRequest()->getParam('id'); - $model = $this->_objectManager->get(\Magento\CheckoutAgreements\Model\Agreement::class)->load($id); - if (!$model->getId()) { + $agreement = $this->agreementRepository->get($id); + if (!$agreement->getAgreementId()) { $this->messageManager->addError(__('This condition no longer exists.')); $this->_redirect('checkout/*/'); return; } try { - $model->delete(); + $this->agreementRepository->delete($agreement); $this->messageManager->addSuccess(__('You deleted the condition.')); $this->_redirect('checkout/*/'); return; - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $this->messageManager->addError($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addError(__('Something went wrong while deleting this condition.')); diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php index 73ac129bc993c..8bec3b581cd54 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Edit.php @@ -6,8 +6,34 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; -class Edit extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement +use Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; +use Magento\CheckoutAgreements\Model\AgreementFactory; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Registry; +use Magento\Framework\App\ObjectManager; +use Magento\CheckoutAgreements\Block\Adminhtml\Agreement\Edit as BlockEdit; + +class Edit extends Agreement { + /** + * @var AgreementFactory + */ + private $agreementFactory; + + /** + * @param Context $context + * @param Registry $coreRegistry + * @param AgreementFactory $agreementFactory + */ + public function __construct( + Context $context, + Registry $coreRegistry, + AgreementFactory $agreementFactory = null + ) { + $this->agreementFactory = $agreementFactory ?: + ObjectManager::getInstance()->get(AgreementFactory::class); + parent::__construct($context, $coreRegistry); + } /** * @return void * @SuppressWarnings(PHPMD.NPathComplexity) @@ -15,7 +41,7 @@ class Edit extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement public function execute() { $id = $this->getRequest()->getParam('id'); - $agreementModel = $this->_objectManager->create(\Magento\CheckoutAgreements\Model\Agreement::class); + $agreementModel = $this->agreementFactory->create(); if ($id) { $agreementModel->load($id); @@ -26,7 +52,7 @@ public function execute() } } - $data = $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getAgreementData(true); + $data = $this->_session->getAgreementData(true); if (!empty($data)) { $agreementModel->setData($data); } @@ -38,7 +64,7 @@ public function execute() $id ? __('Edit Condition') : __('New Condition') )->_addContent( $this->_view->getLayout()->createBlock( - \Magento\CheckoutAgreements\Block\Adminhtml\Agreement\Edit::class + BlockEdit::class )->setData( 'action', $this->getUrl('checkout/*/save') diff --git a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php index 25c034203620b..05a16d3dd4264 100644 --- a/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php +++ b/app/code/Magento/CheckoutAgreements/Controller/Adminhtml/Agreement/Save.php @@ -6,8 +6,35 @@ */ namespace Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; -class Save extends \Magento\CheckoutAgreements\Controller\Adminhtml\Agreement +use Magento\CheckoutAgreements\Controller\Adminhtml\Agreement; +use Magento\CheckoutAgreements\Model\AgreementFactory; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Registry; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; +use Magento\Framework\Exception\LocalizedException; + +class Save extends Agreement { + /** + * @var AgreementFactory + */ + private $agreementFactory; + + /** + * @param Context $context + * @param Registry $coreRegistry + * @param AgreementFactory $agreementFactory + */ + public function __construct( + Context $context, + Registry $coreRegistry, + AgreementFactory $agreementFactory = null + ) { + $this->agreementFactory = $agreementFactory ?: + ObjectManager::getInstance()->get(AgreementFactory::class); + parent::__construct($context, $coreRegistry); + } /** * @return void */ @@ -15,11 +42,11 @@ public function execute() { $postData = $this->getRequest()->getPostValue(); if ($postData) { - $model = $this->_objectManager->get(\Magento\CheckoutAgreements\Model\Agreement::class); + $model = $this->agreementFactory->create(); $model->setData($postData); try { - $validationResult = $model->validateData(new \Magento\Framework\DataObject($postData)); + $validationResult = $model->validateData(new DataObject($postData)); if ($validationResult !== true) { foreach ($validationResult as $message) { $this->messageManager->addError($message); @@ -30,13 +57,13 @@ public function execute() $this->_redirect('checkout/*/'); return; } - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $this->messageManager->addError($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addError(__('Something went wrong while saving this condition.')); } - $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setAgreementData($postData); + $this->_session->setAgreementData($postData); $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); } } diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json b/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json deleted file mode 100644 index 5ba702a5903a5..0000000000000 --- a/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-checkout-agreements", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CheckoutAgreements/etc/db_schema_whitelist.json b/app/code/Magento/CheckoutAgreements/etc/db_schema_whitelist.json index 039bcaade5205..f0be46a47e063 100644 --- a/app/code/Magento/CheckoutAgreements/etc/db_schema_whitelist.json +++ b/app/code/Magento/CheckoutAgreements/etc/db_schema_whitelist.json @@ -1,28 +1,28 @@ { - "checkout_agreement": { - "column": { - "agreement_id": true, - "name": true, - "content": true, - "content_height": true, - "checkbox_text": true, - "is_active": true, - "is_html": true, - "mode": true + "checkout_agreement": { + "column": { + "agreement_id": true, + "name": true, + "content": true, + "content_height": true, + "checkbox_text": true, + "is_active": true, + "is_html": true, + "mode": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true + "checkout_agreement_store": { + "column": { + "agreement_id": true, + "store_id": true + }, + "constraint": { + "PRIMARY": true, + "CHKT_AGRT_STORE_AGRT_ID_CHKT_AGRT_AGRT_ID": true, + "CHECKOUT_AGREEMENT_STORE_STORE_ID_STORE_STORE_ID": true + } } - }, - "checkout_agreement_store": { - "column": { - "agreement_id": true, - "store_id": true - }, - "constraint": { - "PRIMARY": true, - "CHKT_AGRT_STORE_AGRT_ID_CHKT_AGRT_AGRT_ID": true, - "CHECKOUT_AGREEMENT_STORE_STORE_ID_STORE_STORE_ID": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Cms/Api/BlockRepositoryInterface.php b/app/code/Magento/Cms/Api/BlockRepositoryInterface.php index 9b285051fc44b..b713ca91ea852 100644 --- a/app/code/Magento/Cms/Api/BlockRepositoryInterface.php +++ b/app/code/Magento/Cms/Api/BlockRepositoryInterface.php @@ -5,8 +5,6 @@ */ namespace Magento\Cms\Api; -use Magento\Framework\Api\SearchCriteriaInterface; - /** * CMS block CRUD interface. * @api diff --git a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php index 89f6be0525663..e94992ae26b60 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php @@ -78,7 +78,7 @@ protected function _construct() $this->buttonList->add( 'insert_files', - ['class' => 'save no-display primary', 'label' => __('Add Selected'), 'type' => 'button'], + ['class' => 'save no-display action-primary', 'label' => __('Add Selected'), 'type' => 'button'], 0, 0, 'header' diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php index 76b6aeb013285..3aaf40e7d0ab2 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Delete.php @@ -26,18 +26,18 @@ public function execute() $model->load($id); $model->delete(); // display success message - $this->messageManager->addSuccess(__('You deleted the block.')); + $this->messageManager->addSuccessMessage(__('You deleted the block.')); // go to grid return $resultRedirect->setPath('*/*/'); } catch (\Exception $e) { // display error message - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); // go back to edit form return $resultRedirect->setPath('*/*/edit', ['block_id' => $id]); } } // display error message - $this->messageManager->addError(__('We can\'t find a block to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a block to delete.')); // go to grid return $resultRedirect->setPath('*/*/'); } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php index d4c0517621144..8756089063237 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php @@ -42,7 +42,7 @@ public function execute() if ($id) { $model->load($id); if (!$model->getId()) { - $this->messageManager->addError(__('This block no longer exists.')); + $this->messageManager->addErrorMessage(__('This block no longer exists.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('*/*/'); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/InlineEdit.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/InlineEdit.php index e267c568fa9e5..3a7e73fbe5eaa 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/InlineEdit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/InlineEdit.php @@ -46,6 +46,7 @@ public function __construct( /** * @return \Magento\Framework\Controller\ResultInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function execute() { diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php index cff9c8a39b746..92bc7ad71f590 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php @@ -60,7 +60,7 @@ public function execute() $block->delete(); } - $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php index c604e683f9aee..16c99e9857c33 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php @@ -26,21 +26,26 @@ public function execute() $id = $this->getRequest()->getParam('page_id'); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); + if ($id) { $title = ""; try { // init model and delete $model = $this->_objectManager->create(\Magento\Cms\Model\Page::class); $model->load($id); + $title = $model->getTitle(); $model->delete(); + // display success message - $this->messageManager->addSuccess(__('The page has been deleted.')); + $this->messageManager->addSuccessMessage(__('The page has been deleted.')); + // go to grid - $this->_eventManager->dispatch( - 'adminhtml_cmspage_on_delete', - ['title' => $title, 'status' => 'success'] - ); + $this->_eventManager->dispatch('adminhtml_cmspage_on_delete', [ + 'title' => $title, + 'status' => 'success' + ]); + return $resultRedirect->setPath('*/*/'); } catch (\Exception $e) { $this->_eventManager->dispatch( @@ -48,13 +53,15 @@ public function execute() ['title' => $title, 'status' => 'fail'] ); // display error message - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); // go back to edit form return $resultRedirect->setPath('*/*/edit', ['page_id' => $id]); } } + // display error message - $this->messageManager->addError(__('We can\'t find a page to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a page to delete.')); + // go to grid return $resultRedirect->setPath('*/*/'); } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php index ca50a935ce915..6d51c28b6aca7 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php @@ -76,7 +76,7 @@ public function execute() if ($id) { $model->load($id); if (!$model->getId()) { - $this->messageManager->addError(__('This page no longer exists.')); + $this->messageManager->addErrorMessage(__('This page no longer exists.')); /** \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('*/*/'); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php index 6d75f490d42dc..8774d7e69adfe 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/InlineEdit.php @@ -57,6 +57,7 @@ public function __construct( /** * @return \Magento\Framework\Controller\ResultInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function execute() { diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php index a711f20d65639..a1d32aa97a382 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php @@ -59,10 +59,11 @@ public function execute() $page->delete(); } - $this->messageManager->addSuccess(__('A total of %1 record(s) have been deleted.', $collectionSize)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + return $resultRedirect->setPath('*/*/'); } } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php index e39c2115f961e..a85b8ecd5e5a1 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php @@ -59,7 +59,9 @@ public function execute() $item->save(); } - $this->messageManager->addSuccess(__('A total of %1 record(s) have been disabled.', $collection->getSize())); + $this->messageManager->addSuccessMessage( + __('A total of %1 record(s) have been disabled.', $collection->getSize()) + ); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php index 8278c28a1e696..3f26769e4c9e9 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php @@ -59,7 +59,9 @@ public function execute() $item->save(); } - $this->messageManager->addSuccess(__('A total of %1 record(s) have been enabled.', $collection->getSize())); + $this->messageManager->addSuccessMessage( + __('A total of %1 record(s) have been enabled.', $collection->getSize()) + ); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php index 57f92a713ecb0..9b8933c8dba2e 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/PostDataProcessor.php @@ -119,7 +119,7 @@ public function validateRequireEntry(array $data) foreach ($data as $field => $value) { if (in_array($field, array_keys($requiredFields)) && $value == '') { $errorNo = false; - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('To apply changes you should fill in hidden required "%1" field', $requiredFields[$field]) ); } @@ -140,6 +140,7 @@ private function validateData($data, $layoutXmlValidator) if (!empty($data['layout_update_xml']) && !$layoutXmlValidator->isValid($data['layout_update_xml'])) { return false; } + if (!empty($data['custom_layout_update_xml']) && !$layoutXmlValidator->isValid($data['custom_layout_update_xml']) ) { diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Index.php index 525fd31052db8..13765e9faca04 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Index.php @@ -39,7 +39,7 @@ public function execute() try { $this->_objectManager->get(\Magento\Cms\Helper\Wysiwyg\Images::class)->getCurrentPath(); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } $this->_initAction(); /** @var \Magento\Framework\View\Result\Layout $resultLayout */ diff --git a/app/code/Magento/Cms/Controller/Index/Index.php b/app/code/Magento/Cms/Controller/Index/Index.php index 8e20feb0f058f..c027bd1a2b717 100644 --- a/app/code/Magento/Cms/Controller/Index/Index.php +++ b/app/code/Magento/Cms/Controller/Index/Index.php @@ -1,27 +1,55 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Index; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Controller\Result\Forward; +use Magento\Framework\Controller\Result\ForwardFactory; +use Magento\Framework\View\Result\Page as ResultPage; +use Magento\Cms\Helper\Page; +use Magento\Store\Model\ScopeInterface; + class Index extends \Magento\Framework\App\Action\Action { /** - * @var \Magento\Framework\Controller\Result\ForwardFactory + * @var ForwardFactory */ protected $resultForwardFactory; /** - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var Page + */ + private $page; + + /** + * Index constructor. + * + * @param Context $context + * @param ForwardFactory $resultForwardFactory + * @param ScopeConfigInterface|null $scopeConfig + * @param Page|null $page */ public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory + Context $context, + ForwardFactory $resultForwardFactory, + ScopeConfigInterface $scopeConfig = null, + Page $page = null ) { $this->resultForwardFactory = $resultForwardFactory; + $this->scopeConfig = $scopeConfig ? : ObjectManager::getInstance()->get(ScopeConfigInterface::class); + $this->page = $page ? : ObjectManager::getInstance()->get(Page::class); parent::__construct($context); } @@ -29,20 +57,17 @@ public function __construct( * Renders CMS Home page * * @param string|null $coreRoute - * @return \Magento\Framework\Controller\Result\Forward + * + * @return bool|ResponseInterface|Forward|ResultInterface|ResultPage + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($coreRoute = null) { - $pageId = $this->_objectManager->get( - \Magento\Framework\App\Config\ScopeConfigInterface::class - )->getValue( - \Magento\Cms\Helper\Page::XML_PATH_HOME_PAGE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - $resultPage = $this->_objectManager->get(\Magento\Cms\Helper\Page::class)->prepareResultPage($this, $pageId); + $pageId = $this->scopeConfig->getValue(Page::XML_PATH_HOME_PAGE, ScopeInterface::SCOPE_STORE); + $resultPage = $this->page->prepareResultPage($this, $pageId); if (!$resultPage) { - /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ + /** @var Forward $resultForward */ $resultForward = $this->resultForwardFactory->create(); $resultForward->forward('defaultIndex'); return $resultForward; diff --git a/app/code/Magento/Cms/Helper/Page.php b/app/code/Magento/Cms/Helper/Page.php index bf51c5f0210e9..abd260b260b93 100644 --- a/app/code/Magento/Cms/Helper/Page.php +++ b/app/code/Magento/Cms/Helper/Page.php @@ -116,7 +116,7 @@ public function __construct( * Return result CMS page * * @param Action $action - * @param null $pageId + * @param int $pageId * @return \Magento\Framework\View\Result\Page|bool */ public function prepareResultPage(Action $action, $pageId = null) @@ -189,9 +189,7 @@ public function getPageUrl($pageId = null) $page = $this->_pageFactory->create(); if ($pageId !== null && $pageId !== $page->getId()) { $page->setStoreId($this->_storeManager->getStore()->getId()); - if (!$page->load($pageId)) { - return null; - } + $page->load($pageId); } if (!$page->getId()) { diff --git a/app/code/Magento/Cms/Model/Config/Source/Block.php b/app/code/Magento/Cms/Model/Config/Source/Block.php new file mode 100644 index 0000000000000..85dc4e4dd24bf --- /dev/null +++ b/app/code/Magento/Cms/Model/Config/Source/Block.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Model\Config\Source; + +use Magento\Cms\Model\ResourceModel\Block\CollectionFactory; +use Magento\Framework\Data\OptionSourceInterface; + +/** + * Class Block + */ +class Block implements OptionSourceInterface +{ + /** + * @var array + */ + private $options; + + /** + * @var \Magento\Cms\Model\ResourceModel\Block\CollectionFactory + */ + private $collectionFactory; + + /** + * @param \Magento\Cms\Model\ResourceModel\Block\CollectionFactory $collectionFactory + */ + public function __construct( + CollectionFactory $collectionFactory + ) { + $this->collectionFactory = $collectionFactory; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + if (!$this->options) { + $this->options = $this->collectionFactory->create()->toOptionIdArray(); + } + + return $this->options; + } +} diff --git a/app/code/Magento/Cms/Model/ResourceModel/AbstractCollection.php b/app/code/Magento/Cms/Model/ResourceModel/AbstractCollection.php index cd5945c2f47cb..2a67378614445 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/AbstractCollection.php +++ b/app/code/Magento/Cms/Model/ResourceModel/AbstractCollection.php @@ -176,4 +176,32 @@ public function getSelectCountSql() return $countSelect; } + + /** + * Returns pairs identifier - title for unique identifiers + * and pairs identifier|entity_id - title for non-unique after first + * + * @return array + */ + public function toOptionIdArray() + { + $res = []; + $existingIdentifiers = []; + foreach ($this as $item) { + $identifier = $item->getData('identifier'); + + $data['value'] = $identifier; + $data['label'] = $item->getData('title'); + + if (in_array($identifier, $existingIdentifiers)) { + $data['value'] .= '|' . $item->getData($this->getIdFieldName()); + } else { + $existingIdentifiers[] = $identifier; + } + + $res[] = $data; + } + + return $res; + } } diff --git a/app/code/Magento/Cms/Model/ResourceModel/Page/Collection.php b/app/code/Magento/Cms/Model/ResourceModel/Page/Collection.php index 98c071890be46..4ccc2c8f6e77d 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/Page/Collection.php +++ b/app/code/Magento/Cms/Model/ResourceModel/Page/Collection.php @@ -51,34 +51,6 @@ protected function _construct() $this->_map['fields']['store'] = 'store_table.store_id'; } - /** - * Returns pairs identifier - title for unique identifiers - * and pairs identifier|page_id - title for non-unique after first - * - * @return array - */ - public function toOptionIdArray() - { - $res = []; - $existingIdentifiers = []; - foreach ($this as $item) { - $identifier = $item->getData('identifier'); - - $data['value'] = $identifier; - $data['label'] = $item->getData('title'); - - if (in_array($identifier, $existingIdentifiers)) { - $data['value'] .= '|' . $item->getData('page_id'); - } else { - $existingIdentifiers[] = $identifier; - } - - $res[] = $data; - } - - return $res; - } - /** * Set first store flag * diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index 9e095fb6cdec4..dfd55f5346e59 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -567,10 +567,10 @@ public function getThumbnailUrl($filePath, $checkFile = false) * Create thumbnail for image and save it to thumbnails directory * * @param string $source Image path to be resized - * @param bool $keepRation Keep aspect ratio or not + * @param bool $keepRatio Keep aspect ratio or not * @return bool|string Resized filepath or false if errors were occurred */ - public function resizeFile($source, $keepRation = true) + public function resizeFile($source, $keepRatio = true) { $realPath = $this->_directory->getRelativePath($source); if (!$this->_directory->isFile($realPath) || !$this->_directory->isExist($realPath)) { @@ -587,7 +587,7 @@ public function resizeFile($source, $keepRation = true) } $image = $this->_imageFactory->create(); $image->open($source); - $image->keepAspectRatio($keepRation); + $image->keepAspectRatio($keepRatio); $image->resize($this->_resizeParameters['width'], $this->_resizeParameters['height']); $dest = $targetDir . '/' . pathinfo($source, PATHINFO_BASENAME); $image->save($dest); diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSBlockContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSBlockContentActionGroup.xml index 553d851707b96..d2f81c1c24c35 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSBlockContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSBlockContentActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssertBlockContent"> <grabValueFrom selector="{{BlockNewPageBasicFieldsSection.blockTitle}}" stepKey="grabTextFromTitle"/> <assertEquals stepKey="assertTitle" message="pass"> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml index f286c9159c6d8..58318660d2c42 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssertCMSPageContent"> <grabValueFrom selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" stepKey="grabTextFromTitle"/> <assertEquals stepKey="assertTitle" message="pass"> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssignBlockToCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssignBlockToCMSPageActionGroup.xml index 3fa72c2d6b561..5720e79e95abd 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssignBlockToCMSPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssignBlockToCMSPageActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssignBlockToCMSPage"> <arguments> <argument name="Block" defaultValue=""/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml index 06419356d8e84..75e059f620c2d 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="navigateToCreatedCMSPage"> <arguments> <argument name="CMSPage" defaultValue=""/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml new file mode 100644 index 0000000000000..c51e673139af9 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithAllValuesActionGroup.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateNewPageWithAllValues"> + <arguments> + <argument name="PageTitle" type="string"/> + <argument name="ContentHeading" type="string"/> + <argument name="URLKey" type="string"/> + <argument name="selectStoreViewOpt" type="string"/> + <argument name="selectHierarchyOpt" type="string"/> + </arguments> + <amOnPage url="{{CmsNewPagePage.url}}" stepKey="amOnCMSNewPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{PageTitle}}" stepKey="fillFieldTitle"/> + <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickExpandContent"/> + <fillField selector="{{CmsNewPagePageContentSection.contentHeading}}" userInput="{{ContentHeading}}" stepKey="fillFieldContentHeading"/> + <click selector="{{CmsNewPagePageSeoSection.header}}" stepKey="clickExpandSearchEngineOptimization"/> + <fillField selector="{{CmsNewPagePageSeoSection.urlKey}}" userInput="{{URLKey}}" stepKey="fillFieldURLKey"/> + <click selector="{{CmsNewPagePiwSection.header}}" stepKey="clickPageInWebsites"/> + <waitForElementVisible selector="{{CmsNewPagePiwSection.selectStoreView(selectStoreViewOpt)}}" stepKey="waitForStoreGridReload"/> + <clickWithLeftButton selector="{{CmsNewPagePiwSection.selectStoreView(selectStoreViewOpt)}}" stepKey="clickStoreView2"/> + <click selector="{{CmsNewPageHierarchySection.header}}" stepKey="clickHierarchy"/> + <click selector="{{CmsNewPageHierarchySection.selectHierarchy(selectHierarchyOpt)}}" stepKey="clickPageCheckBoxes"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteImageFromStorageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteImageFromStorageActionGroup.xml index cbd239cde80fe..6de6f27e1069f 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteImageFromStorageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteImageFromStorageActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteImageFromStorageActionGroup"> <arguments> <argument name="Image"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml new file mode 100644 index 0000000000000..2a2b2ff15d375 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeletePageByUrlKeyActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeletePageByUrlKeyActionGroup"> + <arguments> + <argument name="UrlKey" type="string"/> + </arguments> + <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnCMSNewPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{CmsPagesPageActionsSection.select(UrlKey)}}" stepKey="clickSelect"/> + <click selector="{{CmsPagesPageActionsSection.delete(UrlKey)}}" stepKey="clickDelete"/> + <waitForElementVisible selector="{{CmsPagesPageActionsSection.deleteConfirm}}" stepKey="waitForOkButtonToBeVisible"/> + <click selector="{{CmsPagesPageActionsSection.deleteConfirm}}" stepKey="clickOkButton"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <see userInput="The page has been deleted." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutBlockContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutBlockContentActionGroup.xml index ef7c925c3f8f7..3ffc999b41abb 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutBlockContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutBlockContentActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="FillOutBlockContent"> <fillField selector="{{BlockNewPageBasicFieldsSection.blockTitle}}" userInput="{{_defaultBlock.title}}" stepKey="fillFieldTitle1"/> <fillField selector="{{BlockNewPageBasicFieldsSection.identifier}}" userInput="{{_defaultBlock.identifier}}" stepKey="fillFieldIdentifier"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml index 5caeadcea282d..e47ff472ccdcb 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/FillOutCMSPageContentActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="FillOutCMSPageContent"> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_duplicatedCMSPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickExpandContentTabForPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/NavigateToMediaFolderActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/NavigateToMediaFolderActionGroup.xml index 031481d90d1bd..3c447f808e721 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/NavigateToMediaFolderActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/NavigateToMediaFolderActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="NavigateToMediaFolderActionGroup"> <arguments> <argument name="FolderName" type="string"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml index 54c4164749152..3016fba6caba8 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="RestoreLayoutSetting"> <waitForElementVisible selector="{{DefaultLayoutsSection.pageLayout}}" stepKey="waittForDefaultCMSLayout" after="expandDefaultLayouts" /> <selectOption selector="{{DefaultLayoutsSection.pageLayout}}" userInput="1 column" stepKey="selectOneColumn" before="clickSaveConfig"/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml index 8656f4e03a21e..e31eb44146af4 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SearchBlockOnGridPageActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="searchBlockOnGridPage"> <arguments> <argument name="Block" defaultValue=""/> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml index 8c1d17c8d9bed..84704b18a40bc 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/SelectImageFromMediaStorageActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="clickBrowseBtnOnUploadPopup"> <click selector="{{MediaGallerySection.Browse}}" stepKey="clickBrowse" /> <waitForPageLoad stepKey="waitForPageLoad1" /> @@ -17,7 +17,6 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoading2" /> <see selector="{{MediaGallerySection.CancelBtn}}" userInput="Cancel" stepKey="seeCancelBtn" /> <see selector="{{MediaGallerySection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn" /> - <see selector="{{MediaGallerySection.InsertFile}}" userInput="Add Selected" stepKey="seeAddSelectedBtn" /> </actionGroup> <actionGroup name="CreateImageFolder"> <arguments> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/VerifyTinyMCEActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/VerifyTinyMCEActionGroup.xml index 24900ad33b560..ed19c291aadd3 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/VerifyTinyMCEActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/VerifyTinyMCEActionGroup.xml @@ -6,10 +6,8 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="VerifyTinyMCEActionGroup"> - <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE" time="30" /> - <seeElement selector="{{TinyMCESection.TinyMCE4}}" stepKey="seeTinyMCE4" /> <seeElement selector="{{TinyMCESection.Style}}" stepKey="assertInfo2"/> <seeElement selector="{{TinyMCESection.Bold}}" stepKey="assertInfo3"/> <seeElement selector="{{TinyMCESection.Italic}}" stepKey="assertInfo4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml index 9e0db2ada4a2f..368df3baa561f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/BlockPageData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultBlock" type="block"> <data key="title">Default Block</data> <data key="identifier" unique="suffix" >block</data> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml index 73e2d6256ce61..d8a8401c31f15 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultCmsPage" type="cms_page"> <data key="title">Test CMS Page</data> <data key="content_heading">Test Content Heading</data> @@ -27,7 +27,7 @@ <data key="identifier" unique="suffix">testpage-</data> </entity> <entity name="simpleCmsPage" type="cms_page"> - <data key="title">Test CMS Page</data> + <data key="title" unique="suffix">Test CMS Page</data> <data key="content_heading">Test Content Heading</data> <data key="content">Sample page content. Yada yada yada.</data> <data key="identifier" unique="suffix">test-page-</data> @@ -75,8 +75,15 @@ <data key="extension">jpg</data> <data key="content">Image content. Yeah.</data> <data key="height">1000</data> + <data key="path">wysiwyg</data> </entity> <entity name="ImageFolder" type="uploadImage"> <data key="name" unique="suffix">Test</data> </entity> + <entity name="_longContentCmsPage" type="cms_page"> + <data key="title">Test CMS Page</data> + <data key="content_heading">Test Content Heading</data> + <data key="content">1<br/>2<br/>3<br/>4<br/>5<br/>6<br/>7<br/>8<br/>9<br/>10<br/>11<br/>12<br/>13<br/>14<br/>15<br/>16<br/>17<br/>18<br/>19<br/>20<br/>line21<br/>22<br/>23<br/>24<br/>25<br/>26<br/>line27<br/>2<br/>3<br/>4<br/>5</data> + <data key="identifier" unique="suffix">test-page-</data> + </entity> </entities> diff --git a/app/code/Magento/Cms/Test/Mftf/Metadata/block-meta.xml b/app/code/Magento/Cms/Test/Mftf/Metadata/block-meta.xml index d764275f7c44b..c007c89b313ae 100644 --- a/app/code/Magento/Cms/Test/Mftf/Metadata/block-meta.xml +++ b/app/code/Magento/Cms/Test/Mftf/Metadata/block-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateBlock" dataType="block" type="create" auth="adminOauth" url="/V1/cmsBlock" method="POST"> <contentType>application/json</contentType> <object key="block" dataType="block"> diff --git a/app/code/Magento/Cms/Test/Mftf/Metadata/cms-meta.xml b/app/code/Magento/Cms/Test/Mftf/Metadata/cms-meta.xml index 495ca2ee0c2fe..44a9d9452e8fe 100644 --- a/app/code/Magento/Cms/Test/Mftf/Metadata/cms-meta.xml +++ b/app/code/Magento/Cms/Test/Mftf/Metadata/cms-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateCMSPage" dataType="cms_page" type="create" auth="adminOauth" url="/V1/cmsPage" method="POST"> <contentType>application/json</contentType> <object key="page" dataType="cms_page"> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsBlocksPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsBlocksPage.xml index 790c2feafcad0..1d9564fee8680 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/CmsBlocksPage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsBlocksPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CmsBlocksPage" url="/cms/block/" area="admin" module="Magento_Cms"> <section name="BlockPageActionsSection"/> </page> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsNewBlockPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewBlockPage.xml index d607c1ccf39af..0a2b7a7ed37b4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/CmsNewBlockPage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewBlockPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CmsNewBlock" area="admin" url="/cms/block/new" module="Magento_Cms"> <section name="CmsNewBlockBlockActionsSection"/> <section name="CmsNewBlockBlockBasicFieldsSection"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsNewPagePage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewPagePage.xml index b165d6c044c2b..c844dc55ea156 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/CmsNewPagePage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsNewPagePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CmsNewPagePage" url="/cms/page/new" area="admin" module="Magento_Cms"> <section name="CmsNewPagePageActionsSection"/> <section name="CmsNewPagePageBasicFieldsSection"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsPagesPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsPagesPage.xml index 9dcb3d608d04e..45ba6eb6cf00c 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/CmsPagesPage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsPagesPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CmsPagesPage" url="/cms/page" area="admin" module="Magento_Cms"> <section name="CmsPagesPageActionsSection"/> </page> diff --git a/app/code/Magento/Cms/Test/Mftf/Page/StorefrontHomePage.xml b/app/code/Magento/Cms/Test/Mftf/Page/StorefrontHomePage.xml index 289d872aad804..07deacfaaef88 100644 --- a/app/code/Magento/Cms/Test/Mftf/Page/StorefrontHomePage.xml +++ b/app/code/Magento/Cms/Test/Mftf/Page/StorefrontHomePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontHomePage" url="/" module="Magento_Cms" area="storefront"> <section name="StorefrontHeaderSection"/> </page> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml index 3fb56e0b179dd..d487517269c01 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/BlockPageActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="BlockPageActionsSection"> <element name="addNewBlock" type="button" selector="#add" timeout="30"/> <element name="select" type="button" selector="//div[text()='{{var1}}']//parent::td//following-sibling::td//button[text()='Select']" parameterized="true"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml index 65ea1226772cf..2e783af6bfc6a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewBlockBlockActionsSection"> <element name="savePage" type="button" selector="#save-button" timeout="30"/> </section> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockBasicFieldsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockBasicFieldsSection.xml index 00b81686f7167..79fc3bac0fb25 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockBasicFieldsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockBasicFieldsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewBlockBlockBasicFieldsSection"> <element name="title" type="input" selector="input[name=title]"/> <element name="identifier" type="input" selector="input[name=identifier]"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPageHierarchySection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPageHierarchySection.xml new file mode 100644 index 0000000000000..a2e4aecf8db2d --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPageHierarchySection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CmsNewPageHierarchySection"> + <element name="header" type="button" selector="div[data-index=hierarchy]" timeout="30"/> + <element name="selectHierarchy" type="button" selector="//a/span[contains(text(),'{{var1}}')]" parameterized="true" timeout="30"/> + </section> +</sections> + + + diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml index 810c482dffd1a..42f8f4d00ee9f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewPagePageActionsSection"> <element name="savePage" type="button" selector="#save_and_close" timeout="10"/> <element name="reset" type="button" selector="#reset"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml index 468dbecb20e02..7288e5d455d52 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageBasicFieldsSection.xml @@ -7,9 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewPagePageBasicFieldsSection"> <element name="pageTitle" type="input" selector="input[name=title]"/> + <element name="RequiredFieldIndicator" type="text" selector=" return window.getComputedStyle(document.querySelector('._required[data-index=title]>.admin__field-label span'), ':after').getPropertyValue('content');"/> <element name="isActive" type="button" selector="//input[@name='is_active' and @value='{{var1}}']" parameterized="true"/> <element name="duplicatedURLKey" type="input" selector="//input[contains(@data-value,'{{var1}}')]" parameterized="true"/> </section> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageContentSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageContentSection.xml index 8015134c90f9c..05a125b9cc6a8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageContentSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageContentSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewPagePageContentSection"> <element name="header" type="button" selector="div[data-index=content]"/> <element name="contentHeading" type="input" selector="input[name=content_heading]"/> @@ -19,6 +19,8 @@ <element name="InsertWidgetBtn" type="button" selector=".action-add-widget"/> <element name="InsertVariableBtn" type="button" selector=".scalable.add-variable.plugin"/> <element name="InsertImageBtn" type="button" selector=".scalable.action-add-image.plugin"/> + <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> + <element name="ImageAlt" type="text" selector="//img[contains(@alt,'{{var1}}')]" parameterized="true"/> </section> <section name="CmsDesignSection"> <element name="DesignTab" type="button" selector="//strong[@class='admin__collapsible-title']//span[text()='Design']"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageSeoSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageSeoSection.xml index 0fe9c01d36fcb..dfd7386e09aba 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageSeoSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageSeoSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsNewPagePageSeoSection"> <element name="header" type="button" selector="div[data-index=search_engine_optimisation]" timeout="30"/> <element name="urlKey" type="input" selector="input[name=identifier]"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml new file mode 100644 index 0000000000000..bd487e3b2c03c --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePiwSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CmsNewPagePiwSection"> + <element name="header" type="button" selector="div[data-index=websites]" timeout="30"/> + <element name="selectStoreView" type="select" selector="//option[contains(text(),'{{var1}}')]" parameterized="true"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml index b27fb84e98a08..de8a2adccc360 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsPagesPageActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CmsPagesPageActionsSection"> <element name="filterButton" type="input" selector="//button[text()='Filters']"/> <element name="URLKey" type="input" selector="//div[@class='admin__form-field-control']/input[@name='identifier']"/> @@ -25,5 +25,7 @@ <element name="firstItemEditButton" type="button" selector=".data-grid .action-select-wrap .action-menu-item[data-action~='item-edit']"/> <element name="activeFilter" type="button" selector="(//div[contains(@class, 'admin__data-grid-filters-current') and contains(@class, '_show')])[1]"/> <element name="savePageSuccessMessage" type="text" selector=".message-success"/> + <element name="delete" type="button" selector="//div[text()='{{var1}}']/parent::td//following-sibling::td[@class='data-grid-actions-cell']//a[text()='Delete']" parameterized="true"/> + <element name="deleteConfirm" type="button" selector=".action-primary.action-accept" timeout="60"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CustomVariableSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CustomVariableSection.xml index 354c86cfc4b3f..1488134ea511d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CustomVariableSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CustomVariableSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CustomVariableSection"> <element name="GridCustomVariableCode" type="text" selector=".//*[@id='customVariablesGrid_table']/tbody//tr//td[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="variableCode" type="input" selector="#code"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontBlockSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontBlockSection.xml index fb4abe30b37af..bd2f9e5a646d5 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontBlockSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontBlockSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBlockSection"> <element name="mediaDescription" type="text" selector=".widget.block.block-static-block>p>img"/> <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml index d7c0a41464d21..7a358c61263d6 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml @@ -7,10 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCMSPageSection"> <element name="mediaDescription" type="text" selector=".column.main>p>img"/> <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> <element name="mainTitle" type="text" selector="#maincontent .page-title"/> + <element name="mainContent" type="text" selector="#maincontent"/> + <element name="footerTop" type="text" selector="footer.page-footer"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml index 154bf33ac5661..d26f7d83616d5 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontHeaderSection"> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml index fef9e2d851652..75a399720cd6a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="TinyMCESection"> <element name="checkIfContentTabOpen" type="button" selector="//span[text()='Content']/parent::strong/parent::*[@data-state-collapsible='closed']"/> <element name="CheckIfTabExpand" type="button" selector="//div[@data-state-collapsible='closed']//span[text()='Content']"/> @@ -36,7 +36,7 @@ <element name="Browse" type="button" selector=".mce-i-browse"/> <element name="BrowseUploadImage" type="file" selector=".fileupload" /> <element name="image" type="text" selector="//small[text()='{{var1}}']" parameterized="true"/> - <element name="imageOrImageCopy" type="text" selector="//img[contains(@alt, '{{arg1}}.{{arg2}}')]|//img[contains(@alt,'{{arg1}}_') and contains(@alt,'.{{arg2}}')]" parameterized="true"/> + <element name="imageOrImageCopy" type="text" selector="//div[contains(@class,'media-gallery-modal')]//img[contains(@alt, '{{arg1}}.{{arg2}}')]|//img[contains(@alt,'{{arg1}}_') and contains(@alt,'.{{arg2}}')]" parameterized="true"/> <element name="imageSelected" type="text" selector="//small[text()='{{var1}}']/parent::*[@class='filecnt selected']" parameterized="true"/> <element name="ImageSource" type="input" selector=".mce-combobox.mce-abs-layout-item.mce-last.mce-has-open" /> <element name="ImageDescription" type="input" selector=".mce-textbox.mce-abs-layout-item.mce-last" /> @@ -74,11 +74,12 @@ </section> <section name="WidgetSection"> <element name="InsertWidgetTitle" type="text" selector="//h1[contains(text(),'Insert Widget')]"/> + <element name="DisplayType" type="select" selector="select[name='parameters[display_type]']"/> <element name="SelectCategoryTitle" type="text" selector="//h1[contains(text(),'Select Category')]"/> <element name="SelectProductTitle" type="text" selector="//h1[contains(text(),'Select Product')]"/> <element name="SelectPageTitle" type="text" selector="//h1[contains(text(),'Select Page')]"/> <element name="SelectBlockTitle" type="text" selector="//h1[contains(text(),'Select Block')]"/> - <element name="InsertWidget" type="button" selector="#insert_button"/> + <element name="InsertWidget" type="button" selector="#insert_button" timeout="30"/> <element name="InsertWidgetBtnDisabled" type="button" selector="//button[@id='insert_button' and contains(@class,'disabled')]"/> <element name="InsertWidgetBtnEnabled" type="button" selector="//button[@id='insert_button' and not(contains(@class,'disabled'))]"/> <element name="CancelBtnEnabled" type="button" selector="//button[@id='reset' and not(contains(@class,'disabled'))]"/> @@ -98,14 +99,10 @@ <element name="PageSize" type="input" selector="input[name='parameters[page_size]']"/> <element name="ProductAttribute" type="multiselect" selector="select[name='parameters[show_attributes][]']" /> <element name="ButtonToShow" type="multiselect" selector="select[name='parameters[show_buttons][]']"/> - <!--Widget on Storefront--> - <element name="CategoryWidget" type="text" selector="//a[@href='http://magento2.vagrant42/{{var1}}.html?___store=default']" parameterized="true"/> - <element name="CMSPageWidget" type="text" selector="//a[@href='http://magento2.vagrant42/home']"/> <!--Compare on Storefront--> <element name="ProductName" type="text" selector=".product.name.product-item-name" /> <element name="CompareBtn" type="button" selector=".action.tocompare"/> <element name="ClearCompare" type="button" selector="#compare-clear-all"/> <element name="AcceptClear" type="button" selector=".action-primary.action-accept" /> - </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index b37c9e97a78fc..9d19f7307dd29 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageToWYSIWYGBlockTest"> <annotations> <features value="Cms"/> @@ -16,6 +16,9 @@ <description value="Admin should be able to add image to WYSIWYG content of Block"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84376"/> + <skip> + <issueId value="MQE-1187" /> + </skip> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index 995f52e42b3a6..205850f888797 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageToWYSIWYGCMSTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml index d0d8edc6abc91..8fea72764f280 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGBlockTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddVariableToWYSIWYGBlockTest"> <annotations> <features value="Cms"/> @@ -15,6 +15,7 @@ <title value="Admin should be able to add variable to WYSIWYG content of Block"/> <description value="You should be able to add variable to WYSIWYG content Block"/> <testCaseId value="MAGETWO-84378"/> + <severity value="AVERAGE"/> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml index a7627b5492d72..9e5eb2558d6f2 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddVariableToWYSIWYGCMSTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml index 4d93980da9a33..ad5e769c61be4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGBlockTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml index 90caf89c6a0ca..ded94eab92042 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml index 89030034dde12..f37038435e109 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 5993c7e2b82f3..5b3679bed77e0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 6d626b3a91734..123d25f92b6b7 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml index 69938147444fe..705f2883f5839 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index c3797d758d860..c89aa27c10e77 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest"> <annotations> <features value="Cms"/> @@ -17,6 +17,8 @@ <description value="Admin should be able to create a CMS page with widget type: Recently Compared Products"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-83792"/> + <!--Skip test due to MC-1284--> + <group value="skip"/> </annotations> <!--Main test--> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml index 1574e6bd3b469..9cdbccd1f8c32 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml index 3b80204f5c3d3..e2c74823dfffb 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsBlockTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateDuplicatedCmsBlockTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml index 73e38fcdad558..fccc5b5980f2b 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCmsPageTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml new file mode 100644 index 0000000000000..d1c44383b4ac1 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontMobileViewValidation"> + <annotations> + <features value="Cms"/> + <title value="Mobile view page footer should stick to the bottom of page on Store front"/> + <description value="Mobile view page footer should stick to the bottom of page on Store front"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94333"/> + <useCaseId value="MAGETWO-93978"/> + <group value="Cms"/> + </annotations> + <before> + <createData entity="_longContentCmsPage" stepKey="createPreReqCMSPage"/> + </before> + <after> + <deleteData createDataKey="createPreReqCMSPage" stepKey="deletePreReqCMSPage"/> + <resizeWindow width="1280" height="1024" stepKey="resizeWindowToDesktop"/> + </after> + <resizeWindow width="375" height="812" stepKey="resizeWindowToMobile"/> + <amOnPage url="$$createPreReqCMSPage.identifier$$" stepKey="amOnPageTestPage"/> + <waitForPageLoad stepKey="waitForPageLoad6" /> + <!--Verifying that Footer is not in visible area by default as the CMS page has lots of content which will occupy entire visible area--> + <executeJS function="return document.querySelector('{{StorefrontCMSPageSection.footerTop}}').getBoundingClientRect().top" stepKey="topOfFooter"/> + <assertGreaterThan stepKey="assertDefaultLoad"> + <actualResult type="variable">topOfFooter</actualResult> + <expectedResult type="string">812</expectedResult> + </assertGreaterThan> + <!--Verifying that even after scroll footer section is below the main content section--> + <scrollTo selector="{{StorefrontCMSPageSection.footerTop}}" stepKey="scrollToFooterSection"/> + <executeJS function="return document.querySelector('{{StorefrontCMSPageSection.footerTop}}').getBoundingClientRect().top" stepKey="topOfTheFooterAfterScroll"/> + <executeJS function="return document.querySelector('{{StorefrontCMSPageSection.mainContent}}').getBoundingClientRect().bottom" stepKey="bottomOfMainContent"/> + <assertGreaterThan stepKey="assertAfterScroll"> + <actualResult type="variable">topOfTheFooterAfterScroll</actualResult> + <expectedResult type="variable">bottomOfMainContent</expectedResult> + </assertGreaterThan> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml index 3d45d4baae748..9ee2055aae650 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyTinyMCEv4IsNativeWYSIWYGOnBlockTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml index 2bfdc5f503720..caad1cabe78c5 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyTinyMCEv4IsNativeWYSIWYGOnCMSPageTest"> <annotations> <features value="Cms"/> diff --git a/app/code/Magento/Cms/Test/Mftf/composer.json b/app/code/Magento/Cms/Test/Mftf/composer.json deleted file mode 100644 index cdf6572dc1460..0000000000000 --- a/app/code/Magento/Cms/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-cms", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cms-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/DeleteTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/DeleteTest.php index ff1ed408eb131..55e8382d9ca23 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/DeleteTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/DeleteTest.php @@ -134,10 +134,10 @@ public function testDeleteAction() ->with($this->blockId); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('You deleted the block.')); $this->messageManagerMock->expects($this->never()) - ->method('addError'); + ->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') @@ -154,10 +154,10 @@ public function testDeleteActionNoId() ->willReturn(null); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('We can\'t find a block to delete.')); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') @@ -181,10 +181,10 @@ public function testDeleteActionThrowsException() ->willThrowException(new \Exception(__($errorMsg))); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($errorMsg); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/EditTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/EditTest.php index 875dde9fb226b..a28a1b793d943 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/EditTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/EditTest.php @@ -139,7 +139,7 @@ public function testEditActionBlockNoExists() ->willReturn(null); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('This block no longer exists.')); $this->resultRedirectFactoryMock->expects($this->atLeastOnce()) diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/MassDeleteTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/MassDeleteTest.php index 2dc14154c85e5..39a7d0d74e4d8 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/MassDeleteTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Block/MassDeleteTest.php @@ -68,9 +68,9 @@ public function testMassDeleteAction() ->willReturn(new \ArrayIterator($collection)); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) have been deleted.', $deletedBlocksCount)); - $this->messageManagerMock->expects($this->never())->method('addError'); + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/DeleteTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/DeleteTest.php index 7f994bf5b3df5..09b36bc41d405 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/DeleteTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/DeleteTest.php @@ -124,10 +124,10 @@ public function testDeleteAction() ->method('delete'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('The page has been deleted.')); $this->messageManagerMock->expects($this->never()) - ->method('addError'); + ->method('addErrorMessage'); $this->eventManagerMock->expects($this->once()) ->method('dispatch') @@ -151,10 +151,10 @@ public function testDeleteActionNoId() ->willReturn(null); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('We can\'t find a page to delete.')); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') @@ -195,10 +195,10 @@ public function testDeleteActionThrowsException() ); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($errorMsg); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/EditTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/EditTest.php index 335abb837523a..5ea5ce5a9fdbb 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/EditTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/EditTest.php @@ -139,7 +139,7 @@ public function testEditActionPageNoExists() ->willReturn(null); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('This page no longer exists.')); $this->resultRedirectFactoryMock->expects($this->atLeastOnce()) diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDeleteTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDeleteTest.php index 8f1a651b0a7e1..f51ab152ba2a4 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDeleteTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDeleteTest.php @@ -68,9 +68,9 @@ public function testMassDeleteAction() ->willReturn(new \ArrayIterator($collection)); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) have been deleted.', $deletedPagesCount)); - $this->messageManagerMock->expects($this->never())->method('addError'); + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDisableTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDisableTest.php index 0185654434be1..5b80dd1873d5c 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDisableTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassDisableTest.php @@ -67,9 +67,9 @@ public function testMassDisableAction() ->willReturn(new \ArrayIterator($collection)); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) have been disabled.', $disabledPagesCount)); - $this->messageManagerMock->expects($this->never())->method('addError'); + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassEnableTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassEnableTest.php index b5907e7b3ffed..16b3dfe4ee638 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassEnableTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Adminhtml/Page/MassEnableTest.php @@ -67,9 +67,9 @@ public function testMassEnableAction() ->willReturn(new \ArrayIterator($collection)); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) have been enabled.', $enabledPagesCount)); - $this->messageManagerMock->expects($this->never())->method('addError'); + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Index/IndexTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Index/IndexTest.php index 8ff206e8a80fc..53064e87c2755 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Index/IndexTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Index/IndexTest.php @@ -42,6 +42,9 @@ class IndexTest extends \PHPUnit\Framework\TestCase */ protected $pageId = 'home'; + /** + * Test setUp + */ protected function setUp() { $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -84,11 +87,16 @@ protected function setUp() 'response' => $responseMock, 'objectManager' => $objectManagerMock, 'request' => $this->requestMock, - 'resultForwardFactory' => $this->forwardFactoryMock + 'resultForwardFactory' => $this->forwardFactoryMock, + 'scopeConfig' => $scopeConfigMock, + 'page' => $this->cmsHelperMock ] ); } + /** + * Controller test + */ public function testExecuteResultPage() { $this->cmsHelperMock->expects($this->once()) @@ -98,6 +106,9 @@ public function testExecuteResultPage() $this->assertSame($this->resultPageMock, $this->controller->execute()); } + /** + * Controller test + */ public function testExecuteResultForward() { $this->forwardMock->expects($this->once()) diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Page/PostDataProcessorTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Page/PostDataProcessorTest.php index 31d99df5f6289..d13dfc628201d 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Page/PostDataProcessorTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Page/PostDataProcessorTest.php @@ -65,7 +65,7 @@ public function testValidateRequireEntry() 'title' => '' ]; $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('To apply changes you should fill in hidden required "%1" field', 'Page Title')); $this->assertFalse($this->postDataProcessor->validateRequireEntry($postData)); diff --git a/app/code/Magento/Cms/Test/Unit/Helper/PageTest.php b/app/code/Magento/Cms/Test/Unit/Helper/PageTest.php index c50f33caa6bc2..19f3b113c5e2c 100644 --- a/app/code/Magento/Cms/Test/Unit/Helper/PageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Helper/PageTest.php @@ -118,6 +118,9 @@ class PageTest extends \PHPUnit\Framework\TestCase */ private $httpRequestMock; + /** + * Test Setup + */ protected function setUp() { $this->actionMock = $this->getMockBuilder(\Magento\Framework\App\Action\Action::class) @@ -478,7 +481,7 @@ public function getPageUrlDataProvider() return [ 'ids NOT EQUAL BUT page->load() NOT SUCCESSFUL' => [ 'pageId' => 123, - 'internalPageId' => 234, + 'internalPageId' => null, 'pageLoadResultIndex' => 0, 'expectedResult' => null, ], diff --git a/app/code/Magento/Cms/Test/Unit/Model/BlockTest.php b/app/code/Magento/Cms/Test/Unit/Model/BlockTest.php new file mode 100644 index 0000000000000..448112b228a0d --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/BlockTest.php @@ -0,0 +1,337 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model; + +use Magento\Cms\Model\Block; +use Magento\Cms\Model\ResourceModel\Block as BlockResource; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Model\Context; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * @covers \Magento\Cms\Model\Block + */ +class BlockTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var Block + */ + private $blockModel; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $eventManagerMock; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var BlockResource|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->resourceMock = $this->createMock(BlockResource::class); + $this->eventManagerMock = $this->createMock(ManagerInterface::class); + $this->contextMock = $this->createMock(Context::class); + $this->contextMock->expects($this->any())->method('getEventDispatcher')->willReturn($this->eventManagerMock); + $this->objectManager = new ObjectManager($this); + $this->blockModel = $this->objectManager->getObject( + Block::class, + [ + 'context' => $this->contextMock, + 'resource' => $this->resourceMock, + ] + ); + } + + /** + * Test beforeSave method + * + * @return void + * + * @throws LocalizedException + */ + public function testBeforeSave() + { + $blockId = 7; + $this->blockModel->setData(Block::BLOCK_ID, $blockId); + $this->blockModel->setData(Block::CONTENT, 'test'); + $this->objectManager->setBackwardCompatibleProperty($this->blockModel, '_hasDataChanges', true); + $this->eventManagerMock->expects($this->atLeastOnce())->method('dispatch'); + $expected = $this->blockModel; + $actual = $this->blockModel->beforeSave(); + self::assertEquals($expected, $actual); + } + + /** + * Test beforeSave method + * + * @return void + * + * @throws LocalizedException + */ + public function testBeforeSaveWithException() + { + $blockId = 10; + $this->blockModel->setData(Block::BLOCK_ID, $blockId); + $this->blockModel->setData(Block::CONTENT, 'Test block_id="' . $blockId . '".'); + $this->objectManager->setBackwardCompatibleProperty($this->blockModel, '_hasDataChanges', false); + $this->eventManagerMock->expects($this->never())->method('dispatch'); + $this->expectException(LocalizedException::class); + $this->blockModel->beforeSave(); + } + + /** + * Test getIdentities method + * + * @return void + */ + public function testGetIdentities() + { + $result = $this->blockModel->getIdentities(); + self::assertInternalType('array', $result); + } + + /** + * Test getId method + * + * @return void + */ + public function testGetId() + { + $blockId = 12; + $this->blockModel->setData(Block::BLOCK_ID, $blockId); + $expected = $blockId; + $actual = $this->blockModel->getId(); + self::assertEquals($expected, $actual); + } + + /** + * Test getIdentifier method + * + * @return void + */ + public function testGetIdentifier() + { + $identifier = 'test01'; + $this->blockModel->setData(Block::IDENTIFIER, $identifier); + $expected = $identifier; + $actual = $this->blockModel->getIdentifier(); + self::assertEquals($expected, $actual); + } + + /** + * Test getTitle method + * + * @return void + */ + public function testGetTitle() + { + $title = 'test02'; + $this->blockModel->setData(Block::TITLE, $title); + $expected = $title; + $actual = $this->blockModel->getTitle(); + self::assertEquals($expected, $actual); + } + + /** + * Test getContent method + * + * @return void + */ + public function testGetContent() + { + $content = 'test03'; + $this->blockModel->setData(Block::CONTENT, $content); + $expected = $content; + $actual = $this->blockModel->getContent(); + self::assertEquals($expected, $actual); + } + + /** + * Test getCreationTime method + * + * @return void + */ + public function testGetCreationTime() + { + $creationTime = 'test04'; + $this->blockModel->setData(Block::CREATION_TIME, $creationTime); + $expected = $creationTime; + $actual = $this->blockModel->getCreationTime(); + self::assertEquals($expected, $actual); + } + + /** + * Test getUpdateTime method + * + * @return void + */ + public function testGetUpdateTime() + { + $updateTime = 'test05'; + $this->blockModel->setData(Block::UPDATE_TIME, $updateTime); + $expected = $updateTime; + $actual = $this->blockModel->getUpdateTime(); + self::assertEquals($expected, $actual); + } + + /** + * Test isActive method + * + * @return void + */ + public function testIsActive() + { + $isActive = true; + $this->blockModel->setData(Block::IS_ACTIVE, $isActive); + $result = $this->blockModel->isActive(); + self::assertTrue($result); + } + + /** + * Test setId method + * + * @return void + */ + public function testSetId() + { + $blockId = 15; + $this->blockModel->setId($blockId); + $expected = $blockId; + $actual = $this->blockModel->getData(Block::BLOCK_ID); + self::assertEquals($expected, $actual); + } + + /** + * Test setIdentifier method + * + * @return void + */ + public function testSetIdentifier() + { + $identifier = 'test06'; + $this->blockModel->setIdentifier($identifier); + $expected = $identifier; + $actual = $this->blockModel->getData(Block::IDENTIFIER); + self::assertEquals($expected, $actual); + } + + /** + * Test setTitle method + * + * @return void + */ + public function testSetTitle() + { + $title = 'test07'; + $this->blockModel->setTitle($title); + $expected = $title; + $actual = $this->blockModel->getData(Block::TITLE); + self::assertEquals($expected, $actual); + } + + /** + * Test setContent method + * + * @return void + */ + public function testSetContent() + { + $content = 'test08'; + $this->blockModel->setContent($content); + $expected = $content; + $actual = $this->blockModel->getData(Block::CONTENT); + self::assertEquals($expected, $actual); + } + + /** + * Test setCreationTime method + * + * @return void + */ + public function testSetCreationTime() + { + $creationTime = 'test09'; + $this->blockModel->setCreationTime($creationTime); + $expected = $creationTime; + $actual = $this->blockModel->getData(Block::CREATION_TIME); + self::assertEquals($expected, $actual); + } + + /** + * Test setUpdateTime method + * + * @return void + */ + public function testSetUpdateTime() + { + $updateTime = 'test10'; + $this->blockModel->setUpdateTime($updateTime); + $expected = $updateTime; + $actual = $this->blockModel->getData(Block::UPDATE_TIME); + self::assertEquals($expected, $actual); + } + + /** + * Test setIsActive method + * + * @return void + */ + public function testSetIsActive() + { + $this->blockModel->setIsActive(false); + $result = $this->blockModel->getData(Block::IS_ACTIVE); + self::assertFalse($result); + } + + /** + * Test getStores method + * + * @return void + */ + public function testGetStores() + { + $stores = [1, 4, 9]; + $this->blockModel->setData('stores', $stores); + $expected = $stores; + $actual = $this->blockModel->getStores(); + self::assertEquals($expected, $actual); + } + + /** + * Test getAvailableStatuses method + * + * @return void + */ + public function testGetAvailableStatuses() + { + $result = $this->blockModel->getAvailableStatuses(); + self::assertInternalType('array', $result); + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Model/Config/Source/BlockTest.php b/app/code/Magento/Cms/Test/Unit/Model/Config/Source/BlockTest.php new file mode 100644 index 0000000000000..b6a91e9f56b30 --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Model/Config/Source/BlockTest.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Test\Unit\Model\Config\Source; + +/** + * Class BlockTest + */ +class BlockTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Cms\Model\ResourceModel\Block\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $collectionFactory; + + /** + * @var \Magento\Cms\Model\Config\Source\Block + */ + protected $block; + + /** + * Set up + * + * @return void + */ + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->collectionFactory = $this->createPartialMock( + \Magento\Cms\Model\ResourceModel\Block\CollectionFactory::class, + ['create'] + ); + + $this->block = $objectManager->getObject( + \Magento\Cms\Model\Config\Source\Block::class, + [ + 'collectionFactory' => $this->collectionFactory, + ] + ); + } + + /** + * Run test toOptionArray method + * + * @return void + */ + public function testToOptionArray() + { + $blockCollectionMock = $this->createMock(\Magento\Cms\Model\ResourceModel\Block\Collection::class); + + $this->collectionFactory->expects($this->once()) + ->method('create') + ->will($this->returnValue($blockCollectionMock)); + + $blockCollectionMock->expects($this->once()) + ->method('toOptionIdArray') + ->will($this->returnValue('return-value')); + + $this->assertEquals('return-value', $this->block->toOptionArray()); + } +} diff --git a/app/code/Magento/Cms/Ui/Component/AddFilterInterface.php b/app/code/Magento/Cms/Ui/Component/AddFilterInterface.php new file mode 100644 index 0000000000000..406b40fbc1647 --- /dev/null +++ b/app/code/Magento/Cms/Ui/Component/AddFilterInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Ui\Component; + +use Magento\Framework\Api\Filter; +use Magento\Framework\Api\Search\SearchCriteriaBuilder; + +/** + * Provides extension point to add additional filters to search criteria. + */ +interface AddFilterInterface +{ + /** + * Adds custom filter to search criteria builder based on received filter. + * + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param Filter $filter + * @return void + */ + public function addFilter(SearchCriteriaBuilder $searchCriteriaBuilder, Filter $filter); +} diff --git a/app/code/Magento/Cms/Ui/Component/DataProvider.php b/app/code/Magento/Cms/Ui/Component/DataProvider.php index 3298d66b0b877..5fc9c5a896037 100644 --- a/app/code/Magento/Cms/Ui/Component/DataProvider.php +++ b/app/code/Magento/Cms/Ui/Component/DataProvider.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Ui\Component; +use Magento\Framework\Api\Filter; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\SearchCriteriaBuilder; use Magento\Framework\App\ObjectManager; @@ -19,6 +20,11 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi */ private $authorization; + /** + * @var AddFilterInterface[] + */ + private $additionalFilterPool; + /** * @param string $name * @param string $primaryFieldName @@ -29,6 +35,8 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi * @param FilterBuilder $filterBuilder * @param array $meta * @param array $data + * @param array $additionalFilterPool + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( $name, @@ -39,7 +47,8 @@ public function __construct( RequestInterface $request, FilterBuilder $filterBuilder, array $meta = [], - array $data = [] + array $data = [], + array $additionalFilterPool = [] ) { parent::__construct( $name, @@ -54,6 +63,7 @@ public function __construct( ); $this->meta = array_replace_recursive($meta, $this->prepareMetadata()); + $this->additionalFilterPool = $additionalFilterPool; } /** @@ -95,4 +105,16 @@ public function prepareMetadata() return $metadata; } + + /** + * @inheritdoc + */ + public function addFilter(Filter $filter) + { + if (!empty($this->additionalFilterPool[$filter->getField()])) { + $this->additionalFilterPool[$filter->getField()]->addFilter($this->searchCriteriaBuilder, $filter); + } else { + parent::addFilter($filter); + } + } } diff --git a/app/code/Magento/Cms/Ui/Component/Page/FulltextFilter.php b/app/code/Magento/Cms/Ui/Component/Page/FulltextFilter.php new file mode 100644 index 0000000000000..9b0c69a4f10c4 --- /dev/null +++ b/app/code/Magento/Cms/Ui/Component/Page/FulltextFilter.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Ui\Component\Page; + +use Magento\Cms\Ui\Component\AddFilterInterface; +use Magento\Framework\Api\Filter; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\Search\SearchCriteriaBuilder; + +/** + * Adds fulltext filter for CMS Page title attribute. + */ +class FulltextFilter implements AddFilterInterface +{ + /** + * @var FilterBuilder + */ + private $filterBuilder; + + /** + * @param FilterBuilder $filterBuilder + */ + public function __construct(FilterBuilder $filterBuilder) + { + $this->filterBuilder = $filterBuilder; + } + + /** + * @inheritdoc + */ + public function addFilter(SearchCriteriaBuilder $searchCriteriaBuilder, Filter $filter) + { + $titleFilter = $this->filterBuilder->setField('title') + ->setValue(sprintf('%%%s%%', $filter->getValue())) + ->setConditionType('like') + ->create(); + $searchCriteriaBuilder->addFilter($titleFilter); + } +} diff --git a/app/code/Magento/Cms/etc/db_schema_whitelist.json b/app/code/Magento/Cms/etc/db_schema_whitelist.json index 4ab3966798b2f..8da44baf71719 100644 --- a/app/code/Magento/Cms/etc/db_schema_whitelist.json +++ b/app/code/Magento/Cms/etc/db_schema_whitelist.json @@ -1,77 +1,77 @@ { - "cms_block": { - "column": { - "block_id": true, - "title": true, - "identifier": true, - "content": true, - "creation_time": true, - "update_time": true, - "is_active": true + "cms_block": { + "column": { + "block_id": true, + "title": true, + "identifier": true, + "content": true, + "creation_time": true, + "update_time": true, + "is_active": true + }, + "index": { + "CMS_BLOCK_TITLE_IDENTIFIER_CONTENT": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "CMS_BLOCK_TITLE_IDENTIFIER_CONTENT": true + "cms_block_store": { + "column": { + "block_id": true, + "store_id": true + }, + "index": { + "CMS_BLOCK_STORE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CMS_BLOCK_STORE_BLOCK_ID_CMS_BLOCK_BLOCK_ID": true, + "CMS_BLOCK_STORE_STORE_ID_STORE_STORE_ID": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "cms_block_store": { - "column": { - "block_id": true, - "store_id": true - }, - "index": { - "CMS_BLOCK_STORE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CMS_BLOCK_STORE_BLOCK_ID_CMS_BLOCK_BLOCK_ID": true, - "CMS_BLOCK_STORE_STORE_ID_STORE_STORE_ID": true - } - }, - "cms_page": { - "column": { - "page_id": true, - "title": true, - "page_layout": true, - "meta_keywords": true, - "meta_description": true, - "identifier": true, - "content_heading": true, - "content": true, - "creation_time": true, - "update_time": true, - "is_active": true, - "sort_order": true, - "layout_update_xml": true, - "custom_theme": true, - "custom_root_template": true, - "custom_layout_update_xml": true, - "custom_theme_from": true, - "custom_theme_to": true, - "meta_title": true - }, - "index": { - "CMS_PAGE_IDENTIFIER": true, - "CMS_PAGE_TITLE_META_KEYWORDS_META_DESCRIPTION_IDENTIFIER_CONTENT": true - }, - "constraint": { - "PRIMARY": true - } - }, - "cms_page_store": { - "column": { - "page_id": true, - "store_id": true - }, - "index": { - "CMS_PAGE_STORE_STORE_ID": true + "cms_page": { + "column": { + "page_id": true, + "title": true, + "page_layout": true, + "meta_keywords": true, + "meta_description": true, + "identifier": true, + "content_heading": true, + "content": true, + "creation_time": true, + "update_time": true, + "is_active": true, + "sort_order": true, + "layout_update_xml": true, + "custom_theme": true, + "custom_root_template": true, + "custom_layout_update_xml": true, + "custom_theme_from": true, + "custom_theme_to": true, + "meta_title": true + }, + "index": { + "CMS_PAGE_IDENTIFIER": true, + "CMS_PAGE_TITLE_META_KEYWORDS_META_DESCRIPTION_IDENTIFIER_CONTENT": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true, - "CMS_PAGE_STORE_PAGE_ID_CMS_PAGE_PAGE_ID": true, - "CMS_PAGE_STORE_STORE_ID_STORE_STORE_ID": true + "cms_page_store": { + "column": { + "page_id": true, + "store_id": true + }, + "index": { + "CMS_PAGE_STORE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CMS_PAGE_STORE_PAGE_ID_CMS_PAGE_PAGE_ID": true, + "CMS_PAGE_STORE_STORE_ID_STORE_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 978d75c9b1e56..b6e13c63302cd 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -225,5 +225,13 @@ <argument name="collectionProcessor" xsi:type="object">Magento\Cms\Model\Api\SearchCriteria\BlockCollectionProcessor</argument> </arguments> </type> + + <type name="Magento\Cms\Ui\Component\DataProvider"> + <arguments> + <argument name="additionalFilterPool" xsi:type="array"> + <item name="fulltext" xsi:type="object">Magento\Cms\Ui\Component\Page\FulltextFilter</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json b/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index 9bed46134f87e..0000000000000 --- a/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-cms-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CmsUrlRewriteGraphQl/Test/Mftf/composer.json b/app/code/Magento/CmsUrlRewriteGraphQl/Test/Mftf/composer.json deleted file mode 100644 index edd1c6b352623..0000000000000 --- a/app/code/Magento/CmsUrlRewriteGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-cms-url-rewrite-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-url-rewrite-graph-ql": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cms-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index 4f6d9c14346f0..479b4f5c21732 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -245,7 +245,7 @@ private function cacheData(array $data) ); $scopes = []; foreach (['websites', 'stores'] as $curScopeType) { - foreach ($data[$curScopeType] as $curScopeId => $curScopeData) { + foreach ($data[$curScopeType] ?? [] as $curScopeId => $curScopeData) { $scopes[$curScopeType][$curScopeId] = 1; $this->cache->save( $this->serializer->serialize($curScopeData), diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index bc1515aadb0ca..c6e2412f7e58f 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -5,6 +5,11 @@ */ namespace Magento\Config\Model; +use Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker; +use Magento\Config\Model\Config\Structure\Element\Group; +use Magento\Config\Model\Config\Structure\Element\Field; +use Magento\Framework\App\ObjectManager; + /** * Backend config model * Used to save configuration @@ -77,6 +82,11 @@ class Config extends \Magento\Framework\DataObject */ protected $_storeManager; + /** + * @var Config\Reader\Source\Deployed\SettingChecker + */ + private $settingChecker; + /** * @param \Magento\Framework\App\Config\ReinitableConfigInterface $config * @param \Magento\Framework\Event\ManagerInterface $eventManager @@ -85,6 +95,7 @@ class Config extends \Magento\Framework\DataObject * @param \Magento\Config\Model\Config\Loader $configLoader * @param \Magento\Framework\App\Config\ValueFactory $configValueFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param Config\Reader\Source\Deployed\SettingChecker|null $settingChecker * @param array $data */ public function __construct( @@ -95,6 +106,7 @@ public function __construct( \Magento\Config\Model\Config\Loader $configLoader, \Magento\Framework\App\Config\ValueFactory $configValueFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, + SettingChecker $settingChecker = null, array $data = [] ) { parent::__construct($data); @@ -105,6 +117,7 @@ public function __construct( $this->_configLoader = $configLoader; $this->_configValueFactory = $configValueFactory; $this->_storeManager = $storeManager; + $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class); } /** @@ -126,11 +139,12 @@ public function save() $oldConfig = $this->_getConfig(true); + /** @var \Magento\Framework\DB\Transaction $deleteTransaction */ $deleteTransaction = $this->_transactionFactory->create(); - /* @var $deleteTransaction \Magento\Framework\DB\Transaction */ + /** @var \Magento\Framework\DB\Transaction $saveTransaction */ $saveTransaction = $this->_transactionFactory->create(); - /* @var $saveTransaction \Magento\Framework\DB\Transaction */ + $changedPaths = []; // Extends for old config data $extraOldGroups = []; @@ -145,6 +159,9 @@ public function save() $saveTransaction, $deleteTransaction ); + + $groupChangedPaths = $this->getChangedPaths($sectionId, $groupId, $groupData, $oldConfig, $extraOldGroups); + $changedPaths = \array_merge($changedPaths, $groupChangedPaths); } try { @@ -157,7 +174,11 @@ public function save() // website and store codes can be used in event implementation, so set them as well $this->_eventManager->dispatch( "admin_system_config_changed_section_{$this->getSection()}", - ['website' => $this->getWebsite(), 'store' => $this->getStore()] + [ + 'website' => $this->getWebsite(), + 'store' => $this->getStore(), + 'changed_paths' => $changedPaths, + ] ); } catch (\Exception $e) { // re-init configuration @@ -168,6 +189,144 @@ public function save() return $this; } + /** + * Map field name if they were cloned + * + * @param Group $group + * @param string $fieldId + * @return string + */ + private function getOriginalFieldId(Group $group, string $fieldId): string + { + if ($group->shouldCloneFields()) { + $cloneModel = $group->getCloneModel(); + + /** @var \Magento\Config\Model\Config\Structure\Element\Field $field */ + foreach ($group->getChildren() as $field) { + foreach ($cloneModel->getPrefixes() as $prefix) { + if ($prefix['field'] . $field->getId() === $fieldId) { + $fieldId = $field->getId(); + break(2); + } + } + } + } + + return $fieldId; + } + + /** + * Get field object + * + * @param string $sectionId + * @param string $groupId + * @param string $fieldId + * @return Field + */ + private function getField(string $sectionId, string $groupId, string $fieldId): Field + { + /** @var \Magento\Config\Model\Config\Structure\Element\Group $group */ + $group = $this->_configStructure->getElement($sectionId . '/' . $groupId); + $fieldPath = $group->getPath() . '/' . $this->getOriginalFieldId($group, $fieldId); + $field = $this->_configStructure->getElement($fieldPath); + + return $field; + } + + /** + * Get field path + * + * @param Field $field + * @param array &$oldConfig Need for compatibility with _processGroup() + * @param array &$extraOldGroups Need for compatibility with _processGroup() + * @return string + */ + private function getFieldPath(Field $field, array &$oldConfig, array &$extraOldGroups): string + { + $path = $field->getGroupPath() . '/' . $field->getId(); + + /** + * Look for custom defined field path + */ + $configPath = $field->getConfigPath(); + if ($configPath && strrpos($configPath, '/') > 0) { + // Extend old data with specified section group + $configGroupPath = substr($configPath, 0, strrpos($configPath, '/')); + if (!isset($extraOldGroups[$configGroupPath])) { + $oldConfig = $this->extendConfig($configGroupPath, true, $oldConfig); + $extraOldGroups[$configGroupPath] = true; + } + $path = $configPath; + } + + return $path; + } + + /** + * Check is config value changed + * + * @param array $oldConfig + * @param string $path + * @param array $fieldData + * @return bool + */ + private function isValueChanged(array $oldConfig, string $path, array $fieldData): bool + { + if (isset($oldConfig[$path]['value'])) { + $result = !isset($fieldData['value']) || $oldConfig[$path]['value'] !== $fieldData['value']; + } else { + $result = empty($fieldData['inherit']); + } + + return $result; + } + + /** + * Get changed paths + * + * @param string $sectionId + * @param string $groupId + * @param array $groupData + * @param array &$oldConfig + * @param array &$extraOldGroups + * @return array + */ + private function getChangedPaths( + string $sectionId, + string $groupId, + array $groupData, + array &$oldConfig, + array &$extraOldGroups + ): array { + $changedPaths = []; + + if (isset($groupData['fields'])) { + foreach ($groupData['fields'] as $fieldId => $fieldData) { + $field = $this->getField($sectionId, $groupId, $fieldId); + $path = $this->getFieldPath($field, $oldConfig, $extraOldGroups); + if ($this->isValueChanged($oldConfig, $path, $fieldData)) { + $changedPaths[] = $path; + } + } + } + + if (isset($groupData['groups'])) { + $subSectionId = $sectionId . '/' . $groupId; + foreach ($groupData['groups'] as $subGroupId => $subGroupData) { + $subGroupChangedPaths = $this->getChangedPaths( + $subSectionId, + $subGroupId, + $subGroupData, + $oldConfig, + $extraOldGroups + ); + $changedPaths = \array_merge($changedPaths, $subGroupChangedPaths); + } + } + + return $changedPaths; + } + /** * Process group data * @@ -182,7 +341,6 @@ public function save() * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function _processGroup( $groupId, @@ -195,92 +353,55 @@ protected function _processGroup( \Magento\Framework\DB\Transaction $deleteTransaction ) { $groupPath = $sectionPath . '/' . $groupId; - $scope = $this->getScope(); - $scopeId = $this->getScopeId(); - $scopeCode = $this->getScopeCode(); - /** - * - * Map field names if they were cloned - */ - /** @var $group \Magento\Config\Model\Config\Structure\Element\Group */ - $group = $this->_configStructure->getElement($groupPath); - // set value for group field entry by fieldname - // use extra memory - $fieldsetData = []; if (isset($groupData['fields'])) { - if ($group->shouldCloneFields()) { - $cloneModel = $group->getCloneModel(); - $mappedFields = []; - - /** @var $field \Magento\Config\Model\Config\Structure\Element\Field */ - foreach ($group->getChildren() as $field) { - foreach ($cloneModel->getPrefixes() as $prefix) { - $mappedFields[$prefix['field'] . $field->getId()] = $field->getId(); - } - } - } + /** @var \Magento\Config\Model\Config\Structure\Element\Group $group */ + $group = $this->_configStructure->getElement($groupPath); + + // set value for group field entry by fieldname + // use extra memory + $fieldsetData = []; foreach ($groupData['fields'] as $fieldId => $fieldData) { - $fieldsetData[$fieldId] = is_array( - $fieldData - ) && isset( - $fieldData['value'] - ) ? $fieldData['value'] : null; + $fieldsetData[$fieldId] = $fieldData['value'] ?? null; } foreach ($groupData['fields'] as $fieldId => $fieldData) { - $originalFieldId = $fieldId; - if ($group->shouldCloneFields() && isset($mappedFields[$fieldId])) { - $originalFieldId = $mappedFields[$fieldId]; + $isReadOnly = $this->settingChecker->isReadOnly( + $groupPath . '/' . $fieldId, + $this->getScope(), + $this->getScopeCode() + ); + + if ($isReadOnly) { + continue; } - /** @var $field \Magento\Config\Model\Config\Structure\Element\Field */ - $field = $this->_configStructure->getElement($groupPath . '/' . $originalFieldId); + $field = $this->getField($sectionPath, $groupId, $fieldId); /** @var \Magento\Framework\App\Config\ValueInterface $backendModel */ - $backendModel = $field->hasBackendModel() ? $field - ->getBackendModel() : $this - ->_configValueFactory - ->create(); + $backendModel = $field->hasBackendModel() + ? $field->getBackendModel() + : $this->_configValueFactory->create(); + if (!isset($fieldData['value'])) { + $fieldData['value'] = null; + } $data = [ 'field' => $fieldId, 'groups' => $groups, 'group_id' => $group->getId(), - 'scope' => $scope, - 'scope_id' => $scopeId, - 'scope_code' => $scopeCode, + 'scope' => $this->getScope(), + 'scope_id' => $this->getScopeId(), + 'scope_code' => $this->getScopeCode(), 'field_config' => $field->getData(), - 'fieldset_data' => $fieldsetData + 'fieldset_data' => $fieldsetData, ]; $backendModel->addData($data); - $this->_checkSingleStoreMode($field, $backendModel); - if (false == isset($fieldData['value'])) { - $fieldData['value'] = null; - } - - $path = $field->getGroupPath() . '/' . $fieldId; - /** - * Look for custom defined field path - */ - if ($field && $field->getConfigPath()) { - $configPath = $field->getConfigPath(); - if (!empty($configPath) && strrpos($configPath, '/') > 0) { - // Extend old data with specified section group - $configGroupPath = substr($configPath, 0, strrpos($configPath, '/')); - if (!isset($extraOldGroups[$configGroupPath])) { - $oldConfig = $this->extendConfig($configGroupPath, true, $oldConfig); - $extraOldGroups[$configGroupPath] = true; - } - $path = $configPath; - } - } - - $inherit = !empty($fieldData['inherit']); - + $path = $this->getFieldPath($field, $extraOldGroups, $oldConfig); $backendModel->setPath($path)->setValue($fieldData['value']); + $inherit = !empty($fieldData['inherit']); if (isset($oldConfig[$path])) { $backendModel->setConfigId($oldConfig[$path]['config_id']); diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php index b86b86ad3bb8c..4ae66bfd9692b 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php +++ b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php @@ -71,7 +71,7 @@ protected function _getCurrencyBase() $this->getScopeId() ); } - return strval($value); + return (string)$value; } /** @@ -88,7 +88,7 @@ protected function _getCurrencyDefault() $this->getScopeId() ); } - return strval($value); + return (string)$value; } /** diff --git a/app/code/Magento/Config/Model/Config/Structure.php b/app/code/Magento/Config/Model/Config/Structure.php index 5a6dbc8e31896..5c74220051ba9 100644 --- a/app/code/Magento/Config/Model/Config/Structure.php +++ b/app/code/Magento/Config/Model/Config/Structure.php @@ -281,6 +281,10 @@ protected function _createEmptyElement(array $pathParts) public function getFieldPathsByAttribute($attributeName, $attributeValue) { $result = []; + if (empty($this->_data['sections'])) { + return $result; + } + foreach ($this->_data['sections'] as $section) { if (!isset($section['children'])) { continue; diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigAdminAccountSharingActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigAdminAccountSharingActionGroup.xml index 51155423e62bf..00bda74c7b5f8 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigAdminAccountSharingActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigAdminAccountSharingActionGroup.xml @@ -7,16 +7,22 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> -<actionGroup name="ConfigAdminAccountSharingActionGroup"> - <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/system_config/edit/section/admin/" stepKey="navigateToConfigurationPage" /> - <waitForPageLoad stepKey="wait1"/> - <conditionalClick stepKey="expandSecurityTab" selector="{{AdminSection.SecurityTab}}" dependentSelector="{{AdminSection.CheckIfTabExpand}}" visible="true" /> - <waitForElementVisible selector="{{AdminSection.AdminAccountSharing}}" stepKey="waitForAdminAccountSharingDrpDown" /> - <uncheckOption selector="{{AdminSection.EnableSystemValue}}" stepKey="uncheckUseSystemValue"/> - <selectOption selector="{{AdminSection.AdminAccountSharing}}" userInput="Yes" stepKey="selectYes"/> - <click selector="{{AdminSection.SecurityTab}}" stepKey="clollapseSecurityTab" /> - <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> -</actionGroup> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ConfigAdminAccountSharingActionGroup"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/system_config/edit/section/admin/" stepKey="navigateToConfigurationPage" /> + <waitForPageLoad stepKey="wait1"/> + <conditionalClick stepKey="expandSecurityTab" selector="{{AdminSection.SecurityTab}}" dependentSelector="{{AdminSection.CheckIfTabExpand}}" visible="true" /> + <waitForElementVisible selector="{{AdminSection.AdminAccountSharing}}" stepKey="waitForAdminAccountSharingDrpDown" /> + <uncheckOption selector="{{AdminSection.EnableSystemValue}}" stepKey="uncheckUseSystemValue"/> + <selectOption selector="{{AdminSection.AdminAccountSharing}}" userInput="Yes" stepKey="selectYes"/> + <click selector="{{AdminSection.SecurityTab}}" stepKey="clollapseSecurityTab" /> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> + </actionGroup> + <actionGroup name="EnableAdminAccountSharingActionGroup"> + <createData stepKey="setConfig" entity="EnableAdminAccountSharing"/> + </actionGroup> + <actionGroup name="DisableAdminAccountSharingActionGroup"> + <createData stepKey="setConfig" entity="DisableAdminAccountSharing"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml index 7bb2441a6a529..06c041fabeb35 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SetTaxClassForShipping"> <amOnPage url="{{AdminSalesTaxClassPage.url}}" stepKey="navigateToSalesTaxPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml index 52adb0b1f50a5..7062d092e7204 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="EnabledWYSIWYG"> <amOnPage url="admin/admin/system_config/edit/section/cms/" stepKey="navigateToConfigurationPage" /> <waitForPageLoad stepKey="wait1"/> @@ -23,10 +23,23 @@ <amOnPage url="admin/admin/system_config/edit/section/cms/" stepKey="navigateToConfigurationPage" /> <waitForPageLoad stepKey="wait3"/> <conditionalClick stepKey="expandWYSIWYGOptions" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> - <waitForElementVisible selector="{{ContentManagementSection.EnableWYSIWYG}}" stepKey="waitForEnableWYSIWYGDropdown2" /> + <waitForElementVisible selector="{{ContentManagementSection.EnableWYSIWYG}}" stepKey="waitForEnableWYSIWYGDropdown2" time="30"/> <uncheckOption selector="{{ContentManagementSection.EnableSystemValue}}" stepKey="uncheckUseSystemValue"/> <selectOption selector="{{ContentManagementSection.EnableWYSIWYG}}" userInput="Disabled Completely" stepKey="selectOption2"/> <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions" /> <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> </actionGroup> + <actionGroup name="UseStaticURLForMediaContentInWYSIWYG"> + <arguments> + <argument name="value" defaultValue="Yes" type="string"/> + </arguments> + <amOnPage url="{{ConfigurationStoresPage.url}}" stepKey="navigateToWYSIWYGConfigPage1"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <conditionalClick stepKey="expandWYSIWYGOptions" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> + <waitForElementVisible selector="{{ContentManagementSection.EnableWYSIWYG}}" stepKey="waitForEnableWYSIWYGDropdown1" /> + <selectOption selector="{{ContentManagementSection.StaticURL}}" userInput="{{value}}" stepKey="selectOption1"/> + <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions" /> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> + <waitForPageLoad stepKey="waitForPageLoad2" /> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWebUrlOptionsActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWebUrlOptionsActionGroup.xml index 056b89624a2f5..f1e0ea6b7e175 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWebUrlOptionsActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWebUrlOptionsActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="EnableWebUrlOptions"> <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml index c3c0430a3d58c..fc5b5580d617c 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/GeneralConfigurationActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="NavigateToDefaultLayoutsSetting"> <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml index 670cd236be8be..e9e899a68c33e 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="RestoreLayoutSetting"> <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml index 172ace8b18c11..f29ee1a407203 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/SwitcherActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SwitchToVersion4ActionGroup"> <amOnPage url="{{ConfigurationStoresPage.url}}" stepKey="navigateToWYSIWYGConfigPage1"/> <waitForPageLoad stepKey="waitForConfigPageToLoad"/> diff --git a/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml b/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml new file mode 100644 index 0000000000000..75dc19dc99c8e --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminAccountSharingYes" type="admin_account_sharing_value"> + <data key="value">Yes</data> + </entity> + <entity name="AdminAccountSharingNo" type="admin_account_sharing_value"> + <data key="value">No</data> + </entity> + <entity name="EnableAdminAccountSharing" type="admin_account_sharing_config"> + <requiredEntity type="admin_account_sharing_value">AdminAccountSharingYes</requiredEntity> + </entity> + <entity name="DisableAdminAccountSharing" type="admin_account_sharing_config"> + <requiredEntity type="admin_account_sharing_value">AdminAccountSharingNo</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml new file mode 100644 index 0000000000000..37b8414d1f396 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="AdminAccountSharingConfig" dataType="admin_account_sharing_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/admin/" method="POST"> + <object key="groups" dataType="admin_account_sharing_config"> + <object key="security" dataType="admin_account_sharing_config"> + <object key="fields" dataType="admin_account_sharing_config"> + <object key="admin_account_sharing" dataType="admin_account_sharing_value"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml b/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml index c24e578c9109c..1e60158e4ac96 100644 --- a/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml +++ b/app/code/Magento/Config/Test/Mftf/Page/AdminConfigPage.xml @@ -5,13 +5,16 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminConfigPage" url="admin/system_config/" area="admin" module="Magento_Config"> <section name="AdminConfigSection"/> </page> <page name="AdminContentManagementPage" url="admin/system_config/edit/section/cms/" area="admin" module="Magento_Config"> <section name="ContentManagementSection"/> </page> + <page name="CatalogConfigPage" url="admin/system_config/edit/section/catalog/" area="admin" module="Magento_Config"> + <section name="ContentManagementSection"/> + </page> <page name="AdminSalesTaxClassPage" url="admin/system_config/edit/section/tax/" area="admin" module="Magento_Config"> <section name="SalesTaxClassSection"/> </page> diff --git a/app/code/Magento/Config/Test/Mftf/Page/AdminSalesConfigPage.xml b/app/code/Magento/Config/Test/Mftf/Page/AdminSalesConfigPage.xml new file mode 100644 index 0000000000000..7897a181ff405 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Page/AdminSalesConfigPage.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminSalesConfigPage" url="admin/system_config/edit/section/sales/{{var1}}" area="admin" parameterized="true" module="Magento_Config"> + <section name="AdminSalesConfigSection"/> + </page> +</pages> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml index a1b8c2f62f7da..dbede5491e011 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminConfigSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminConfigSection"> <element name="advancedReportingMenuItem" type="text" selector="//a[contains(concat(' ',normalize-space(@class),' '),'item-nav')]/span[text()='Advanced Reporting']"/> <element name="advancedReportingService" type="select" selector="#analytics_general_enabled"/> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml new file mode 100644 index 0000000000000..55679fdb1524d --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminSalesConfigSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminSalesConfigSection"> + <element name="enableMAPUseSystemValue" type="checkbox" selector="#sales_msrp_enabled_inherit"/> + <element name="enableMAPSelect" type="select" selector="#sales_msrp_enabled"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminSection.xml index 8278c6366b68a..7b6c9f8ab3b79 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/AdminSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminSection"> <element name="CheckIfTabExpand" type="button" selector="#admin_security-head:not(.open)"/> <element name="SecurityTab" type="button" selector="#admin_security-head"/> diff --git a/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml new file mode 100644 index 0000000000000..3b1de96abc8a2 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CatalogSection"> + <element name="storefront" type="select" selector="#catalog_frontend-head"/> + <element name="CheckIfTabExpand" type="button" selector="#catalog_frontend-head:not(.open)"/> + </section> +</sections> diff --git a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml index b1454ff07ee9e..0008bf1ec9bca 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ContentManagementSection"> <element name="WYSIWYGOptions" type="button" selector="#cms_wysiwyg-head"/> <element name="CheckIfTabExpand" type="button" selector="#cms_wysiwyg-head:not(.open)"/> @@ -15,6 +15,7 @@ <element name="EnableWYSIWYG" type="button" selector="#cms_wysiwyg_enabled"/> <element name="SwitcherSystemValue" type="button" selector="#cms_wysiwyg_editor_inherit"/> <element name="Switcher" type="button" selector="#cms_wysiwyg_editor" /> + <element name="StaticURL" type="button" selector="#cms_wysiwyg_use_static_urls_in_catalog" /> <element name="Save" type="button" selector="#save"/> </section> <section name="WebSection"> diff --git a/app/code/Magento/Config/Test/Mftf/Section/SalesConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/SalesConfigSection.xml index f1520f5813e6d..fbe1fd77eaa46 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/SalesConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/SalesConfigSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="SalesConfigSection"> <element name="TaxClassesTab" type="button" selector="#tax_classes-head"/> <element name="CheckIfTaxClassesTabExpand" type="button" selector="#tax_classes-head:not(.open)"/> diff --git a/app/code/Magento/Config/Test/Mftf/Section/StoreConfigSection.xml b/app/code/Magento/Config/Test/Mftf/Section/StoreConfigSection.xml index 0ff3f3ca55d22..52fc0018a949d 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/StoreConfigSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/StoreConfigSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StoreConfigSection"> <element name="CheckIfTabExpand" type="button" selector="#general_store_information-head:not(.open)"/> <element name="StoreInformation" type="button" selector="#general_store_information-head"/> diff --git a/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml new file mode 100644 index 0000000000000..66aacf706b039 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="VerifyAllowDynamicMediaURLsSettingIsRemoved"> + <annotations> + <features value="Backend"/> + <stories value="Dynamic Media URL"/> + <title value="Verify that Allow Dynamic Media URLs setting is removed from configuration page"/> + <description value="Verify that Allow Dynamic Media URLs setting is removed from configuration page"/> + <severity value="CRITICAL"/> + <useCaseId value="MC-1364"/> + <testCaseId value="MC-3178"/> + <group value="configuration"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{CatalogConfigPage.url}}" stepKey="navigateToConfigurationPage" /> + <waitForPageLoad stepKey="waitForPageLoad"/> + <conditionalClick stepKey="expandStorefrontTab" selector="{{CatalogSection.storefront}}" dependentSelector="{{CatalogSection.CheckIfTabExpand}}" visible="true" /> + <dontSee stepKey="dontSeeDynamicMediaURLsSetting" userInput="Allow Dynamic Media URLs"/> + </test> +</tests> diff --git a/app/code/Magento/Config/Test/Mftf/composer.json b/app/code/Magento/Config/Test/Mftf/composer.json deleted file mode 100644 index 0dacef66e5dce..0000000000000 --- a/app/code/Magento/Config/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-config", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/functional-test-module-deploy": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php index 2ddbbd5ffe1e8..d0568f48ded1e 100644 --- a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php @@ -60,6 +60,11 @@ class ConfigTest extends \PHPUnit\Framework\TestCase */ protected $_configStructure; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $_settingsChecker; + protected function setUp() { $this->_eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); @@ -79,7 +84,7 @@ protected function setUp() $this->_transFactoryMock = $this->createPartialMock( \Magento\Framework\DB\TransactionFactory::class, - ['create'] + ['create', 'addObject'] ); $this->_appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class); $this->_configLoaderMock = $this->createPartialMock( @@ -90,6 +95,9 @@ protected function setUp() $this->_storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); + $this->_settingsChecker = $this + ->createMock(\Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker::class); + $this->_model = new \Magento\Config\Model\Config( $this->_appConfigMock, $this->_eventManagerMock, @@ -97,7 +105,8 @@ protected function setUp() $this->_transFactoryMock, $this->_configLoaderMock, $this->_dataFactoryMock, - $this->_storeManager + $this->_storeManager, + $this->_settingsChecker ); } @@ -149,55 +158,96 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent() $this->_model->save(); } - public function testSaveToCheckScopeDataSet() + public function testDoNotSaveReadOnlyFields() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->_settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true)); $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_eventManagerMock->expects( - $this->at(0) - )->method( - 'dispatch' - )->with( - $this->equalTo('admin_system_config_changed_section_'), - $this->arrayHasKey('website') - ); - - $this->_eventManagerMock->expects( - $this->at(0) - )->method( - 'dispatch' - )->with( - $this->equalTo('admin_system_config_changed_section_'), - $this->arrayHasKey('store') - ); + $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); + $this->_model->setSection('section'); $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class); + $group->method('getPath')->willReturn('section/1'); $field = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Field::class); + $field->method('getGroupPath')->willReturn('section/1'); + $field->method('getId')->willReturn('key'); + + $this->_configStructure->expects($this->at(0)) + ->method('getElement') + ->with('section/1') + ->will($this->returnValue($group)); + $this->_configStructure->expects($this->at(1)) + ->method('getElement') + ->with('section/1') + ->will($this->returnValue($group)); + $this->_configStructure->expects($this->at(2)) + ->method('getElement') + ->with('section/1/key') + ->will($this->returnValue($field)); - $this->_configStructure->expects( - $this->at(0) - )->method( - 'getElement' - )->with( - '/1' - )->will( - $this->returnValue($group) + $backendModel = $this->createPartialMock( + \Magento\Framework\App\Config\Value::class, + ['addData'] ); + $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); - $this->_configStructure->expects( - $this->at(1) - )->method( - 'getElement' - )->with( - '/1/key' - )->will( - $this->returnValue($field) - ); + $this->_transFactoryMock->expects($this->never())->method('addObject'); + $backendModel->expects($this->never())->method('addData'); + + $this->_model->save(); + } + + public function testSaveToCheckScopeDataSet() + { + $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); + $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + + $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + + $this->_eventManagerMock->expects($this->at(0)) + ->method('dispatch') + ->with( + $this->equalTo('admin_system_config_changed_section_section'), + $this->arrayHasKey('website') + ); + $this->_eventManagerMock->expects($this->at(0)) + ->method('dispatch') + ->with( + $this->equalTo('admin_system_config_changed_section_section'), + $this->arrayHasKey('store') + ); + + $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class); + $group->method('getPath')->willReturn('section/1'); + + $field = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Field::class); + $field->method('getGroupPath')->willReturn('section/1'); + $field->method('getId')->willReturn('key'); + + $this->_configStructure->expects($this->at(0)) + ->method('getElement') + ->with('section/1') + ->will($this->returnValue($group)); + $this->_configStructure->expects($this->at(1)) + ->method('getElement') + ->with('section/1') + ->will($this->returnValue($group)); + $this->_configStructure->expects($this->at(2)) + ->method('getElement') + ->with('section/1/key') + ->will($this->returnValue($field)); + $this->_configStructure->expects($this->at(3)) + ->method('getElement') + ->with('section/1') + ->will($this->returnValue($group)); + $this->_configStructure->expects($this->at(4)) + ->method('getElement') + ->with('section/1/key') + ->will($this->returnValue($field)); $website = $this->createMock(\Magento\Store\Model\Website::class); $website->expects($this->any())->method('getCode')->will($this->returnValue('website_code')); @@ -206,19 +256,16 @@ public function testSaveToCheckScopeDataSet() $this->_storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true)); $this->_model->setWebsite('website'); - + $this->_model->setSection('section'); $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); $backendModel = $this->createPartialMock( \Magento\Framework\App\Config\Value::class, ['setPath', 'addData', '__sleep', '__wakeup'] ); - $backendModel->expects( - $this->once() - )->method( - 'addData' - )->with( - [ + $backendModel->expects($this->once()) + ->method('addData') + ->with([ 'field' => 'key', 'groups' => [1 => ['fields' => ['key' => ['data']]]], 'group_id' => null, @@ -227,17 +274,11 @@ public function testSaveToCheckScopeDataSet() 'scope_code' => 'website_code', 'field_config' => null, 'fieldset_data' => ['key' => null], - ] - ); - $backendModel->expects( - $this->once() - )->method( - 'setPath' - )->with( - '/key' - )->will( - $this->returnValue($backendModel) - ); + ]); + $backendModel->expects($this->once()) + ->method('setPath') + ->with('section/1/key') + ->will($this->returnValue($backendModel)); $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); @@ -295,7 +336,7 @@ public function setDataByPathWrongDepthDataProvider() 'depth 2' => ['section/group', "Your configuration depth is 2 for path 'section/group'"], 'depth 1' => ['section', "Your configuration depth is 1 for path 'section'"], 'depth 4' => ['section/group/field/sub-field', "Your configuration depth is 4 for path" - . " 'section/group/field/sub-field'", ], + . " 'section/group/field/sub-field'", ], ]; } } diff --git a/app/code/Magento/Config/etc/db_schema_whitelist.json b/app/code/Magento/Config/etc/db_schema_whitelist.json index 7383843c3bf5d..850e160bc732f 100644 --- a/app/code/Magento/Config/etc/db_schema_whitelist.json +++ b/app/code/Magento/Config/etc/db_schema_whitelist.json @@ -1,15 +1,15 @@ { - "core_config_data": { - "column": { - "config_id": true, - "scope": true, - "scope_id": true, - "path": true, - "value": true - }, - "constraint": { - "PRIMARY": true, - "CORE_CONFIG_DATA_SCOPE_SCOPE_ID_PATH": true + "core_config_data": { + "column": { + "config_id": true, + "scope": true, + "scope_id": true, + "path": true, + "value": true + }, + "constraint": { + "PRIMARY": true, + "CORE_CONFIG_DATA_SCOPE_SCOPE_ID_PATH": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Config/etc/system_file.xsd b/app/code/Magento/Config/etc/system_file.xsd index 5a2b915262a9a..f1688b2e35371 100644 --- a/app/code/Magento/Config/etc/system_file.xsd +++ b/app/code/Magento/Config/etc/system_file.xsd @@ -474,7 +474,7 @@ </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[A-Za-z0-9\\:]+" /> + <xs:pattern value="[A-Za-z0-9_\\:]+" /> <xs:minLength value="5" /> </xs:restriction> </xs:simpleType> diff --git a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php index 151bf5aa9263e..fdbd8560c2664 100644 --- a/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableImportExport/Model/Import/Product/Type/Configurable.php @@ -25,6 +25,12 @@ class Configurable extends \Magento\CatalogImportExport\Model\Import\Product\Typ /** * Error codes. */ + const ERROR_ATTRIBUTE_CODE_DOES_NOT_EXIST = 'attrCodeDoesNotExist'; + + const ERROR_ATTRIBUTE_CODE_NOT_GLOBAL_SCOPE = 'attrCodeNotGlobalScope'; + + const ERROR_ATTRIBUTE_CODE_NOT_TYPE_SELECT = 'attrCodeNotTypeSelect'; + const ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER = 'attrCodeIsNotSuper'; const ERROR_INVALID_OPTION_VALUE = 'invalidOptionValue'; @@ -39,10 +45,19 @@ class Configurable extends \Magento\CatalogImportExport\Model\Import\Product\Typ * Validation failure message template definitions * * @var array + * + * Note: Some of these messages exceed maximum limit of 120 characters per line. Split up accordingly. */ protected $_messageTemplates = [ - self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER => 'Attribute with code "%s" is not super', - self::ERROR_INVALID_OPTION_VALUE => 'Invalid option value for attribute "%s"', + self::ERROR_ATTRIBUTE_CODE_DOES_NOT_EXIST => 'Column configurable_variations: Attribute with code ' . + '"%s" does not exist or is missing from product attribute set', + self::ERROR_ATTRIBUTE_CODE_NOT_GLOBAL_SCOPE => 'Column configurable_variations: Attribute with code ' . + '"%s" is not super - it needs to have Global Scope', + self::ERROR_ATTRIBUTE_CODE_NOT_TYPE_SELECT => 'Column configurable_variations: Attribute with code ' . + '"%s" is not super - it needs to be Input Type of Dropdown, Visual Swatch or Text Swatch', + self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER => 'Column configurable_variations: Attribute with code ' . + '"%s" is not super', + self::ERROR_INVALID_OPTION_VALUE => 'Column configurable_variations: Invalid option value for attribute "%s"', self::ERROR_INVALID_WEBSITE => 'Invalid website code for super attribute', self::ERROR_DUPLICATED_VARIATIONS => 'SKU %s contains duplicated variations', self::ERROR_UNIDENTIFIABLE_VARIATION => 'Configurable variation "%s" is unidentifiable', @@ -289,10 +304,11 @@ protected function _isParticularAttributesValid(array $rowData, $rowNum) { if (!empty($rowData['_super_attribute_code'])) { $superAttrCode = $rowData['_super_attribute_code']; - if (!$this->_isAttributeSuper($superAttrCode)) { - // check attribute superity - $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER, $rowNum, $superAttrCode); + // Identify reason why attribute is not super: + if (!$this->identifySuperAttributeError($superAttrCode, $rowNum)) { + $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_IS_NOT_SUPER, $rowNum, $superAttrCode); + } return false; } elseif (isset($rowData['_super_attribute_option']) && strlen($rowData['_super_attribute_option'])) { $optionKey = strtolower($rowData['_super_attribute_option']); @@ -305,6 +321,66 @@ protected function _isParticularAttributesValid(array $rowData, $rowNum) return true; } + /** + * Identify exactly why a super attribute code is not super. + * + * @param string $superAttrCode + * @param int $rowNum + * @return bool + */ + private function identifySuperAttributeError($superAttrCode, $rowNum) + { + // This attribute code is not a super attribute. Need to give a clearer message why? + $reasonFound = false; + $codeExists = false; + + // Does this attribute code exist? + $sourceAttribute = $this->doesSuperAttributeExist($superAttrCode); + if (is_array($sourceAttribute)) { + $codeExists = true; + // Does attribute have the correct settings? + if (isset($sourceAttribute['is_global']) && $sourceAttribute['is_global'] !== '1') { + $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_NOT_GLOBAL_SCOPE, $rowNum, $superAttrCode); + $reasonFound = true; + } elseif (isset($sourceAttribute['type']) && $sourceAttribute['type'] !== 'select') { + $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_NOT_TYPE_SELECT, $rowNum, $superAttrCode); + $reasonFound = true; + } + } + + if ($codeExists === false) { + $this->_entityModel->addRowError(self::ERROR_ATTRIBUTE_CODE_DOES_NOT_EXIST, $rowNum, $superAttrCode); + $reasonFound = true; + } + + return $reasonFound; + } + + /** + * Does the super attribute exist in the current attribute set? + * + * @param string $superAttrCode + * @return array + */ + private function doesSuperAttributeExist($superAttrCode) + { + $returnAttributeArray = null; + if (is_array(self::$commonAttributesCache)) { + $filteredAttribute = array_filter( + self::$commonAttributesCache, + function ($element) use ($superAttrCode) { + return $element['code'] == $superAttrCode; + } + ); + + // Return the first element of the filtered array (if found). + if (count($filteredAttribute)) { + $returnAttributeArray = array_shift($filteredAttribute); + } + } + return $returnAttributeArray; + } + /** * Array of SKU to array of super attribute values for all products. * diff --git a/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json b/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json deleted file mode 100644 index 74322a53512f9..0000000000000 --- a/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableProduct/Block/Cart/Item/Renderer/Configurable.php b/app/code/Magento/ConfigurableProduct/Block/Cart/Item/Renderer/Configurable.php index 2698da8dcb368..3b657dd1ab2d0 100644 --- a/app/code/Magento/ConfigurableProduct/Block/Cart/Item/Renderer/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Block/Cart/Item/Renderer/Configurable.php @@ -19,6 +19,8 @@ class Configurable extends Renderer implements IdentityInterface { /** * Path in config to the setting which defines if parent or child product should be used to generate a thumbnail. + * @deprecated moved to model because of class refactoring + * @see \Magento\ConfigurableProduct\Model\Product\Configuration\Item\ItemProductResolver::CONFIG_THUMBNAIL_SOURCE */ const CONFIG_THUMBNAIL_SOURCE = 'checkout/cart/configurable_product_image'; @@ -55,29 +57,6 @@ public function getOptionList() return $this->_productConfig->getOptions($this->getItem()); } - /** - * {@inheritdoc} - */ - public function getProductForThumbnail() - { - /** - * Show parent product thumbnail if it must be always shown according to the related setting in system config - * or if child thumbnail is not available - */ - if ($this->_scopeConfig->getValue( - self::CONFIG_THUMBNAIL_SOURCE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) == ThumbnailSource::OPTION_USE_PARENT_IMAGE - || !($this->getChildProduct() - && $this->getChildProduct()->getThumbnail() && $this->getChildProduct()->getThumbnail() != 'no_selection') - ) { - $product = $this->getProduct(); - } else { - $product = $this->getChildProduct(); - } - return $product; - } - /** * Return identifiers for produced content * diff --git a/app/code/Magento/ConfigurableProduct/CustomerData/ConfigurableItem.php b/app/code/Magento/ConfigurableProduct/CustomerData/ConfigurableItem.php deleted file mode 100644 index 3a9ed653305c5..0000000000000 --- a/app/code/Magento/ConfigurableProduct/CustomerData/ConfigurableItem.php +++ /dev/null @@ -1,85 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\ConfigurableProduct\CustomerData; - -use Magento\Catalog\Model\Config\Source\Product\Thumbnail as ThumbnailSource; -use Magento\Checkout\CustomerData\DefaultItem; - -/** - * Configurable item - */ -class ConfigurableItem extends DefaultItem -{ - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - protected $_scopeConfig; - - /** - * @param \Magento\Catalog\Helper\Image $imageHelper - * @param \Magento\Msrp\Helper\Data $msrpHelper - * @param \Magento\Framework\UrlInterface $urlBuilder - * @param \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool - * @param \Magento\Checkout\Helper\Data $checkoutHelper - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\Escaper|null $escaper - */ - public function __construct( - \Magento\Catalog\Helper\Image $imageHelper, - \Magento\Msrp\Helper\Data $msrpHelper, - \Magento\Framework\UrlInterface $urlBuilder, - \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool, - \Magento\Checkout\Helper\Data $checkoutHelper, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\Escaper $escaper = null - ) { - parent::__construct( - $imageHelper, - $msrpHelper, - $urlBuilder, - $configurationPool, - $checkoutHelper, - $escaper - ); - $this->_scopeConfig = $scopeConfig; - } - - /** - * {@inheritdoc} - */ - protected function getProductForThumbnail() - { - /** - * Show parent product thumbnail if it must be always shown according to the related setting in system config - * or if child thumbnail is not available - */ - $config = $this->_scopeConfig->getValue( - \Magento\ConfigurableProduct\Block\Cart\Item\Renderer\Configurable::CONFIG_THUMBNAIL_SOURCE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - - $product = $config == ThumbnailSource::OPTION_USE_PARENT_IMAGE - || (!$this->getChildProduct()->getThumbnail() || $this->getChildProduct()->getThumbnail() == 'no_selection') - ? $this->getProduct() - : $this->getChildProduct(); - - return $product; - } - - /** - * Get item configurable child product - * - * @return \Magento\Catalog\Model\Product - */ - protected function getChildProduct() - { - if ($option = $this->item->getOptionByCode('simple_product')) { - return $option->getProduct(); - } - return $this->getProduct(); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Configuration/Item/ItemProductResolver.php b/app/code/Magento/ConfigurableProduct/Model/Product/Configuration/Item/ItemProductResolver.php new file mode 100644 index 0000000000000..6c33ecc138aea --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Configuration/Item/ItemProductResolver.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Product\Configuration\Item; + +use Magento\Catalog\Model\Config\Source\Product\Thumbnail; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Catalog\Model\Product; + +/** + * {@inheritdoc} + */ +class ItemProductResolver implements ItemResolverInterface +{ + /** + * Path in config to the setting which defines if parent or child product should be used to generate a thumbnail. + */ + const CONFIG_THUMBNAIL_SOURCE = 'checkout/cart/configurable_product_image'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * {@inheritdoc} + */ + public function getFinalProduct(ItemInterface $item) : ProductInterface + { + /** + * Show parent product thumbnail if it must be always shown according to the related setting in system config + * or if child thumbnail is not available. + */ + $parentProduct = $item->getProduct(); + $finalProduct = $parentProduct; + $childProduct = $this->getChildProduct($item); + if ($childProduct !== $parentProduct) { + $configValue = $this->scopeConfig->getValue( + self::CONFIG_THUMBNAIL_SOURCE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $childThumb = $childProduct->getData('thumbnail'); + $finalProduct = + ($configValue == Thumbnail::OPTION_USE_PARENT_IMAGE) || (!$childThumb || $childThumb == 'no_selection') + ? $parentProduct + : $childProduct; + } + return $finalProduct; + } + + /** + * Get item configurable child product. + * + * @param ItemInterface $item + * @return Product + */ + private function getChildProduct(ItemInterface $item) : Product + { + $option = $item->getOptionByCode('simple_product'); + $product = $item->getProduct(); + if ($option) { + $product = $option->getProduct(); + } + return $product; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 29583231a764a..19de63b7a976c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -926,6 +926,8 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p return $result; } } + } elseif (is_string($result)) { + return __($result)->render(); } } diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php index 617297e545b7d..7306942c3c49b 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php @@ -18,7 +18,7 @@ class Attribute extends \Magento\Framework\Model\AbstractExtensibleModel implements \Magento\ConfigurableProduct\Api\Data\OptionInterface { - /**#@+ + /** * Constants for field names */ const KEY_ATTRIBUTE_ID = 'attribute_id'; @@ -27,9 +27,10 @@ class Attribute extends \Magento\Framework\Model\AbstractExtensibleModel impleme const KEY_IS_USE_DEFAULT = 'is_use_default'; const KEY_VALUES = 'values'; const KEY_PRODUCT_ID = 'product_id'; - /**#@-*/ - /**#@-*/ + /** + * @var MetadataPool|\Magento\Framework\EntityManager\MetadataPool + */ private $metadataPool; /** diff --git a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php index 4450bbd75e574..56993ecec1fbf 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php +++ b/app/code/Magento/ConfigurableProduct/Model/Quote/Item/CartItemProcessor.php @@ -59,7 +59,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function convertToBuyRequest(CartItemInterface $cartItem) { @@ -69,7 +69,7 @@ public function convertToBuyRequest(CartItemInterface $cartItem) if (is_array($options)) { $requestData = []; foreach ($options as $option) { - $requestData['super_attribute'][$option->getOptionId()] = $option->getOptionValue(); + $requestData['super_attribute'][$option->getOptionId()] = (string) $option->getOptionValue(); } return $this->objectFactory->create($requestData); } @@ -78,7 +78,7 @@ public function convertToBuyRequest(CartItemInterface $cartItem) } /** - * {@inheritdoc} + * @inheritdoc */ public function processOptions(CartItemInterface $cartItem) { diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index 087931ebe5dcc..81e2e99bfe93a 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -5,91 +5,176 @@ */ namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Store\Api\StoreResolverInterface; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query\BaseFinalPrice; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; /** * Configurable Products Price Indexer Resource model + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice +class Configurable implements DimensionalIndexerInterface { /** - * @param null|int|array $entityIds - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable + * @var BaseFinalPrice */ - protected function reindex($entityIds = null) - { - if ($this->hasEntity() || !empty($entityIds)) { - $this->prepareFinalPriceDataForType($entityIds, $this->getTypeId()); - $this->_applyCustomOption(); - $this->_applyConfigurableOption($entityIds); - $this->_movePriceDataToIndexTable($entityIds); - } + private $baseFinalPrice; - return $this; - } + /** + * @var IndexTableStructureFactory + */ + private $indexTableStructureFactory; /** - * Retrieve table name for custom option temporary aggregation data - * - * @return string + * @var TableMaintainer */ - protected function _getConfigurableOptionAggregateTable() - { - return $this->tableStrategy->getTableName('catalog_product_index_price_cfg_opt_agr'); - } + private $tableMaintainer; /** - * Retrieve table name for custom option prices data - * - * @return string + * @var MetadataPool */ - protected function _getConfigurableOptionPriceTable() - { - return $this->tableStrategy->getTableName('catalog_product_index_price_cfg_opt'); + private $metadataPool; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resource; + + /** + * @var bool + */ + private $fullReindexAction; + + /** + * @var string + */ + private $connectionName; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var BasePriceModifier + */ + private $basePriceModifier; + + /** + * @param BaseFinalPrice $baseFinalPrice + * @param IndexTableStructureFactory $indexTableStructureFactory + * @param TableMaintainer $tableMaintainer + * @param MetadataPool $metadataPool + * @param \Magento\Framework\App\ResourceConnection $resource + * @param BasePriceModifier $basePriceModifier + * @param bool $fullReindexAction + * @param string $connectionName + */ + public function __construct( + BaseFinalPrice $baseFinalPrice, + IndexTableStructureFactory $indexTableStructureFactory, + TableMaintainer $tableMaintainer, + MetadataPool $metadataPool, + \Magento\Framework\App\ResourceConnection $resource, + BasePriceModifier $basePriceModifier, + $fullReindexAction = false, + $connectionName = 'indexer' + ) { + $this->baseFinalPrice = $baseFinalPrice; + $this->indexTableStructureFactory = $indexTableStructureFactory; + $this->tableMaintainer = $tableMaintainer; + $this->connectionName = $connectionName; + $this->metadataPool = $metadataPool; + $this->resource = $resource; + $this->fullReindexAction = $fullReindexAction; + $this->basePriceModifier = $basePriceModifier; } /** - * Prepare table structure for custom option temporary aggregation data + * {@inheritdoc} * - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable + * @throws \Exception */ - protected function _prepareConfigurableOptionAggregateTable() + public function executeByDimensions(array $dimensions, \Traversable $entityIds) { - $this->getConnection()->delete($this->_getConfigurableOptionAggregateTable()); - return $this; + $this->tableMaintainer->createMainTmpTable($dimensions); + + $temporaryPriceTable = $this->indexTableStructureFactory->create([ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ]); + $select = $this->baseFinalPrice->getQuery( + $dimensions, + \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE, + iterator_to_array($entityIds) + ); + $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); + $this->tableMaintainer->getConnection()->query($query); + + $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds)); + $this->applyConfigurableOption($temporaryPriceTable, $dimensions, iterator_to_array($entityIds)); } /** - * Prepare table structure for custom option prices data + * Apply configurable option * - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable + * @param IndexTableStructure $temporaryPriceTable + * @param array $dimensions + * @param array $entityIds + * + * @return $this + * @throws \Exception */ - protected function _prepareConfigurableOptionPriceTable() - { - $this->getConnection()->delete($this->_getConfigurableOptionPriceTable()); + private function applyConfigurableOption( + IndexTableStructure $temporaryPriceTable, + array $dimensions, + array $entityIds + ) { + $temporaryOptionsTableName = 'catalog_product_index_price_cfg_opt_temp'; + $this->getConnection()->createTemporaryTableLike( + $temporaryOptionsTableName, + $this->getTable('catalog_product_index_price_cfg_opt_tmp'), + true + ); + + $this->fillTemporaryOptionsTable($temporaryOptionsTableName, $dimensions, $entityIds); + $this->updateTemporaryTable($temporaryPriceTable->getTableName(), $temporaryOptionsTableName); + + $this->getConnection()->delete($temporaryOptionsTableName); + return $this; } /** - * Calculate minimal and maximal prices for configurable product options - * and apply it to final price + * Put data into catalog product price indexer config option temp table + * + * @param string $temporaryOptionsTableName + * @param array $dimensions + * @param array $entityIds * - * @param array|null $entityIds - * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable + * @return void + * @throws \Exception */ - protected function _applyConfigurableOption($entityIds = null) + private function fillTemporaryOptionsTable(string $temporaryOptionsTableName, array $dimensions, array $entityIds) { - $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); - $connection = $this->getConnection(); - $copTable = $this->_getConfigurableOptionPriceTable(); - $finalPriceTable = $this->_getDefaultFinalPriceTable(); + $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); $linkField = $metadata->getLinkField(); - $this->_prepareConfigurableOptionPriceTable(); - - $select = $connection->select()->from( - ['i' => $this->getIdxTable()], + $select = $this->getConnection()->select()->from( + ['i' => $this->getMainTable($dimensions)], [] )->join( ['l' => $this->getTable('catalog_product_super_link')], @@ -107,7 +192,6 @@ protected function _applyConfigurableOption($entityIds = null) 'MIN(final_price)', 'MAX(final_price)', 'MIN(tier_price)', - ] )->group( ['le.entity_id', 'customer_group_id', 'website_id'] @@ -115,31 +199,77 @@ protected function _applyConfigurableOption($entityIds = null) if ($entityIds !== null) { $select->where('le.entity_id IN (?)', $entityIds); } + $query = $select->insertFromSelect($temporaryOptionsTableName); + $this->getConnection()->query($query); + } - $query = $select->insertFromSelect($copTable); - $connection->query($query); - - $table = ['i' => $finalPriceTable]; - $select = $connection->select()->join( - ['io' => $copTable], + /** + * Update data in the catalog product price indexer temp table + * + * @param string $temporaryPriceTableName + * @param string $temporaryOptionsTableName + * + * @return void + */ + private function updateTemporaryTable(string $temporaryPriceTableName, string $temporaryOptionsTableName) + { + $table = ['i' => $temporaryPriceTableName]; + $selectForCrossUpdate = $this->getConnection()->select()->join( + ['io' => $temporaryOptionsTableName], 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' . ' AND i.website_id = io.website_id', [] ); // adds price of custom option, that was applied in DefaultPrice::_applyCustomOption - $select->columns( + $selectForCrossUpdate->columns( [ - 'min_price' => new \Zend_Db_Expr('i.min_price - i.orig_price + io.min_price'), - 'max_price' => new \Zend_Db_Expr('i.max_price - i.orig_price + io.max_price'), + 'min_price' => new \Zend_Db_Expr('i.min_price - i.price + io.min_price'), + 'max_price' => new \Zend_Db_Expr('i.max_price - i.price + io.max_price'), 'tier_price' => 'io.tier_price', ] ); - $query = $select->crossUpdateFromSelect($table); - $connection->query($query); + $query = $selectForCrossUpdate->crossUpdateFromSelect($table); + $this->getConnection()->query($query); + } + + /** + * Get main table + * + * @param array $dimensions + * @return string + */ + private function getMainTable($dimensions) + { + if ($this->fullReindexAction) { + return $this->tableMaintainer->getMainReplicaTable($dimensions); + } + return $this->tableMaintainer->getMainTable($dimensions); + } + + /** + * Get connection + * + * return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \DomainException + */ + private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface + { + if ($this->connection === null) { + $this->connection = $this->resource->getConnection($this->connectionName); + } - $connection->delete($copTable); + return $this->connection; + } - return $this; + /** + * Get table + * + * @param string $tableName + * @return string + */ + private function getTable($tableName) + { + return $this->resource->getTableName($tableName, $this->connectionName); } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 3d42217de5f91..5581fcc07b861 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -64,7 +64,7 @@ public function resolvePrice(\Magento\Framework\Pricing\SaleableInterface $produ foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) { $productPrice = $this->priceResolver->resolvePrice($subProduct); - $price = $price ? min($price, $productPrice) : $productPrice; + $price = isset($price) ? min($price, $productPrice) : $productPrice; } return (float)$price; diff --git a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php index c9fea3e74d3d2..f69d8529fb801 100644 --- a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php +++ b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/InstallInitialConfigurableAttributes.php @@ -51,6 +51,7 @@ public function apply() $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); $attributes = [ 'country_of_manufacture', + 'manufacturer', 'minimal_price', 'msrp', 'msrp_display_actual_price_type', diff --git a/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php new file mode 100644 index 0000000000000..c9a2f7d373a63 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Setup/Patch/Data/UpdateManufacturerAttribute.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Setup\Patch\Data; + +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Class UpdateManufacturerAttribute + * @package Magento\ConfigurableProduct\Setup\Patch + */ +class UpdateManufacturerAttribute implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var EavSetupFactory + */ + private $eavSetupFactory; + + /** + * UpdateTierPriceAttribute constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + * @param EavSetupFactory $eavSetupFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + EavSetupFactory $eavSetupFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]); + $relatedProductTypes = explode( + ',', + $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'manufacturer', 'apply_to') + ); + + if (!in_array(Configurable::TYPE_CODE, $relatedProductTypes)) { + $relatedProductTypes[] = Configurable::TYPE_CODE; + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'manufacturer', + 'apply_to', + implode(',', $relatedProductTypes) + ); + } + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return [ + InstallInitialConfigurableAttributes::class, + ]; + } + + /** + * {@inheritdoc}\ + */ + public static function getVersion() + { + return '2.2.1'; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index 63ef6cb99f8c1..9ebadc236bb31 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Filter the product grid and view expected products--> <actionGroup name="viewConfigurableProductInAdminGrid"> <arguments> @@ -60,6 +60,7 @@ <fillField userInput="{{product.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillPrice"/> <fillField userInput="{{product.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" stepKey="fillQuantity"/> <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[{{category.name}}]" stepKey="fillCategory"/> + <selectOption userInput="{{product.visibility}}" selector="{{AdminProductFormSection.visibility}}" stepKey="fillVisibility"/> <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> <fillField userInput="{{product.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> @@ -103,4 +104,24 @@ <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> <seeInTitle userInput="{{product.name}}" stepKey="seeProductNameInTitle"/> </actionGroup> + + <actionGroup name="createConfigurationsForAttribute"> + <arguments> + <argument name="attributeCode" type="string" defaultValue="SomeString"/> + </arguments> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="{{attributeCode}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="99" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml new file mode 100644 index 0000000000000..95533057608f2 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="GotoCatalogProductsPage"> + + <!--Click on Catalog item--> + <click stepKey="clickOnCatalogItem" selector="{{CatalogProductsSection.catalogItem}}"/> + <waitForPageLoad stepKey="waitForCatalogLoad"/> + + <!--Click on Products item--> + <click stepKey="clickOnProductItem" selector="{{CatalogProductsSection.productItem}}"/> + <waitForPageLoad stepKey="waitForCatalogProductPageLoad"/> + + <!--Assert we have gone desired page successfully--> + <seeInCurrentUrl stepKey="assertWeAreOnTheCatalogProductPage" url="{{assertionData.catalogProduct}}"/> + + </actionGroup> + + <actionGroup name="GotoConfigurableProductPage"> + + <!--Click on Add product item--> + <click stepKey="clickOnAddProductItem" selector="{{ConfigurableProductSection.addProductItem}}"/> + + <!--Click on Configuration Product item--> + <click stepKey="clickOnConfigurationProductItem" selector="{{ConfigurableProductSection.configProductItem}}"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + + <!--Assert we have gone desired page successfully--> + <seeInCurrentUrl stepKey="assertWeAreOnTheConfigurableProductPage" url="{{assertionData.configurableProduct}}"/> + + </actionGroup> + + <actionGroup name="FillAllRequiredFields"> + + <!--Fill In Product Name Fields--> + <fillField stepKey="fillInProductNameFields" selector="{{NewProduct.productName}}" userInput="{{NewProductsData.productName}}"/> + + <!--Fill In Price Fields--> + <fillField stepKey="fillInPriceFields" selector="{{NewProduct.price}}" userInput="{{NewProductsData.price}}"/> + + <!--Fill In Weight Fields--> + <fillField stepKey="fillInWeightFields" selector="{{NewProduct.weight}}" userInput="{{NewProductsData.weight}}"/> + + <!--Click "Create Configurations" button in configurations field--> + <click stepKey="clickOnCreateConfigurationsButton" selector="{{NewProduct.createConfigurationButton}}"/> + <waitForPageLoad stepKey="waitForCreateProductConfigurationsPageLoad"/> + + <!--Click "Create New Attribute" button--> + <click stepKey="clickOnCreateNewAttributeButton" selector="{{NewProduct.createNewAttributeButton}}"/> + <waitForPageLoad stepKey="waitForNewAttributePageLoad"/> + + </actionGroup> + + + <actionGroup name="CreateNewAttribute"> + + <switchToIFrame stepKey="NewAttributePage" selector="{{NewProduct.newAttributeIFrame}}"/> + + <!--Fill In Product Name Fields--> + <fillField stepKey="fillInDefaultLabelField" selector="{{NewProduct.defaultLabel}}" userInput="{{NewProductsData.defaultLabel}}"/> + + <!--Add option 1 to attribute--> + <click selector="{{NewProduct.addOptionButton}}" stepKey="clickAddOption1"/> + <waitForPageLoad stepKey="waitForOption1"/> + <fillField stepKey="fillInAdminFieldRed" selector="{{NewProduct.adminFieldRed}}" userInput="{{NewProductsData.adminFieldRed}}"/> + <fillField stepKey="fillInDefaultStoreViewFieldRed" selector="{{NewProduct.defaultStoreViewFieldRed}}" userInput="{{NewProductsData.defaultStoreViewFieldRed}}"/> + + <!--Add option 2 to attribute--> + <click selector="{{NewProduct.addOptionButton}}" stepKey="clickAddOption2"/> + <waitForPageLoad stepKey="waitForOption2"/> + <fillField stepKey="fillInAdminFieldBlue" selector="{{NewProduct.adminFieldBlue}}" userInput="{{NewProductsData.adminFieldBlue}}"/> + <fillField stepKey="fillInDefaultStoreViewFieldBlue" selector="{{NewProduct.defaultStoreViewFieldBlue}}" userInput="{{NewProductsData.defaultStoreViewFieldBlue}}"/> + + <!--Add option 3 to attribute--> + <click selector="{{NewProduct.addOptionButton}}" stepKey="clickAddOption3"/> + <waitForPageLoad stepKey="waitForOption3"/> + <fillField stepKey="fillInAdminFieldYellow" selector="{{NewProduct.adminFieldYellow}}" userInput="{{NewProductsData.adminFieldYellow}}"/> + <fillField stepKey="fillInDefaultStoreViewFieldYellow" selector="{{NewProduct.defaultStoreViewFieldYellow}}" userInput="{{NewProductsData.defaultStoreViewFieldYellow}}"/> + + <!--Add option 4 to attribute--> + <click selector="{{NewProduct.addOptionButton}}" stepKey="clickAddOption4"/> + <waitForPageLoad stepKey="waitForOption4"/> + <fillField stepKey="fillInAdminFieldGreen" selector="{{NewProduct.adminFieldGreen}}" userInput="{{NewProductsData.adminFieldGreen}}"/> + <fillField stepKey="fillInDefaultStoreViewFieldGreen" selector="{{NewProduct.defaultStoreViewFieldGreen}}" userInput="{{NewProductsData.defaultStoreViewFieldGreen}}"/> + + <!--Add option 5 to attribute--> + <click selector="{{NewProduct.addOptionButton}}" stepKey="clickAddOption5"/> + <waitForPageLoad stepKey="waitForOption5"/> + <fillField stepKey="fillInAdminFieldBlack" selector="{{NewProduct.adminFieldBlack}}" userInput="{{NewProductsData.adminFieldBlack}}"/> + <fillField stepKey="fillInDefaultStoreViewFieldBlack" selector="{{NewProduct.defaultStoreViewFieldBlack}}" userInput="{{NewProductsData.defaultStoreViewFieldBlack}}"/> + + <!--Click Save Attribute button--> + <click selector="{{NewProduct.saveAttributeButton}}" stepKey="clickSaveAttributeButton"/> + <waitForPageLoad stepKey="waitForSavingSettings"/> + + <!--Select created Attribute --> + <click selector="{{ConfigurableProductSection.selectCreatedAttribute}}" stepKey="selectCreatedAttribute"/> + + <!--Click Next button--> + <click selector="{{ConfigurableProductSection.nextButton}}" stepKey="clickNextButton"/> + <waitForPageLoad stepKey="waitForNextPageLoaded"/> + + <!--Select all the options of all the attributes button--> + <click selector="{{CreateProductConfigurations.checkboxRed}}" stepKey="selectCheckboxRed"/> + <click selector="{{CreateProductConfigurations.checkboxBlue}}" stepKey="selectCheckboxBlue"/> + <click selector="{{CreateProductConfigurations.checkboxYellow}}" stepKey="selectCheckboxYellow"/> + <click selector="{{CreateProductConfigurations.checkboxGreen}}" stepKey="selectCheckboxGreen"/> + <click selector="{{CreateProductConfigurations.checkboxBlack}}" stepKey="selectCheckboxBlack"/> + + <!--Click Next button--> + <click selector="{{ConfigurableProductSection.nextButton}}" stepKey="clickNextButton2"/> + <waitForPageLoad stepKey="waitForBulkImagesPricePageLoaded"/> + + <!--Click Next button--> + <click selector="{{ConfigurableProductSection.nextButton}}" stepKey="clickNextButton3"/> + <waitForPageLoad stepKey="waitForSummaryPageLoaded"/> + + <!--Click Generate Configure button--> + <click selector="{{ConfigurableProductSection.generateConfigure}}" stepKey="generateConfigure"/> + <waitForPageLoad stepKey="waitForGenerateConfigure"/> + + <!-- This Error message shouldn't appear: Test will pass when bug will be fixed--> + <dontSee selector="{{CreateProductConfigurations.errorMessage}}" userInput="{{assertionData.errorMessage}}" stepKey="dontSeeError"/> + + <!--Close frame--> + <conditionalClick selector="{{ConfigurableProductSection.closeFrame}}" dependentSelector="{{ConfigurableProductSection.closeFrame}}" visible="1" stepKey="closeFrame"/> + <waitForPageLoad stepKey="waitForClosingFrame"/> + + </actionGroup> + + <actionGroup name="DeleteCreatedAttribute"> + + <!--Click on Stores item--> + <click stepKey="clickOnStoresItem" selector="{{CatalogProductsSection.storesItem}}"/> + <waitForPageLoad stepKey="waitForNavigationPanel"/> + + <!--Click on Products item--> + <waitForElementVisible selector="{{CatalogProductsSection.storesProductItem}}" stepKey="waitForCatalogLoad"/> + <click stepKey="clickOnStoresProductItem" selector="{{CatalogProductsSection.storesProductItem}}"/> + <waitForPageLoad stepKey="waitForStoresProductPageLoad"/> + + <!--Click on created Attribute --> + <fillField stepKey="searchProductDefaultLabel" selector="{{CatalogProductsSection.searchDefaultLabelField}}" userInput="{{NewProductsData.defaultLabel}}"/> + <click stepKey="clickSearchButton" selector="{{CatalogProductsSection.searchButton}}"/> + <waitForPageLoad stepKey="waitForCreatedAttributeLoad"/> + <click stepKey="clickOnCreatedAttributeItem" selector="{{CatalogProductsSection.createdAttributeItem}}"/> + <waitForPageLoad stepKey="waitForAttributePropertiesPageLoad"/> + + <!--Click on Delete Attribute item--> + <click stepKey="clickOnDeleteAttributeItem" selector="{{CatalogProductsSection.deleteAttributeItem}}"/> + <waitForPageLoad stepKey="waitForDeletedDialogOpened"/> + + <!--Click on OK button--> + <click stepKey="clickOnOKButton" selector="{{CatalogProductsSection.okButton}}"/> + <waitForPageLoad stepKey="waitFordAttributeDeleted"/> + <see userInput="You deleted the product attribute." stepKey="seeDeletedTheProductAttributeMessage"/> + + <!-- Click Reset Filter button--> + <click stepKey="clickResetFilterButton" selector="{{CatalogProductsSection.resetFilter}}"/> + <waitForPageLoad stepKey="waitForAllFilterReset"/> + + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductCheckoutActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductCheckoutActionGroup.xml index f272fa8ea733c..f88ed5e1b02f3 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductCheckoutActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductCheckoutActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check configurable product in checkout cart items --> <actionGroup name="CheckConfigurableProductInCheckoutCartItemsActionGroup"> <arguments> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 9c160d72acc8d..39c206e365a2d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check configurable product on the category page --> <actionGroup name="StorefrontCheckCategoryConfigurableProduct"> <arguments> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml index 62e03b62151a5..a0c82ae356e22 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check the configurable product in comparison page --> <actionGroup name="StorefrontCheckCompareConfigurableProductActionGroup"> <arguments> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml index 968f8c490af8d..0a8d8e56426ba 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check the configurable product on the product page --> <actionGroup name="StorefrontCheckConfigurableProduct"> <arguments> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml index cc88a2c6147ef..e07b97c63a92b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check Configurable Product in the Cart --> <actionGroup name="StorefrontCheckCartConfigurableProductActionGroup"> <arguments> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml new file mode 100644 index 0000000000000..73a668fd2fefd --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NewProductsData" type="user"> + <data key="productName" unique="prefix">Shoes</data> + <data key="price">60</data> + <data key="weight">100</data> + <data key="defaultLabel">design</data> + <data key="adminFieldRed">red</data> + <data key="defaultStoreViewFieldRed">red123</data> + <data key="adminFieldBlue">blue</data> + <data key="defaultStoreViewFieldBlue">blue123</data> + <data key="adminFieldYellow">yellow</data> + <data key="defaultStoreViewFieldYellow">yellow123</data> + <data key="adminFieldGreen">green</data> + <data key="defaultStoreViewFieldGreen">green123</data> + <data key="adminFieldBlack">black</data> + <data key="defaultStoreViewFieldBlack">black123</data> + <data key="attributeCodeField">bug91524</data> + </entity> + + <entity name="assertionData" type="assertion"> + <data key="catalogProduct">product</data> + <data key="configurableProduct">configurable</data> + <data key="errorMessage">element.disabled is not a function</data> + </entity> + +</entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml index f92b388b46649..286c11ad5f30a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="BaseConfigurableProduct" type="product"> <data key="sku" unique="suffix">configurable</data> <data key="type_id">configurable</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml index 21dcf998a6399..7555337db8e02 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ConfigurableProductTwoOptions" type="ConfigurableProductOption"> <var key="attribute_id" entityKey="attribute_id" entityType="ProductAttribute" /> <data key="label">option</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConstData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConstData.xml index 974be1e14a389..7e21729ba15c4 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConstData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="CONST" type="CONST"> <data key="three">3</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml index f99f960f6a945..9342172f7d4df 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="colorProductAttribute" type="product_attribute"> <data key="default_label" unique="suffix">Color</data> <data key="input_type">Dropdown</data> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml index 54d489a446fd7..2e4788823a28b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ValueIndex1" type="ValueIndex"> <var key="value_index" entityKey="value" entityType="ProductAttributeOption"/> </entity> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_add_child-meta.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_add_child-meta.xml index 6a77e97d8f276..ec4da787541f9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_add_child-meta.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_add_child-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="ConfigurableProductAddChild" dataType="ConfigurableProductAddChild" type="create" auth="adminOauth" url="/V1/configurable-products/{sku}/child" method="POST"> <contentType>application/json</contentType> <field key="childSku">string</field> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_options-meta.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_options-meta.xml index 37e6be683c2fe..4d894f780092d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_options-meta.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/configurable_product_options-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateConfigurableProductOption" dataType="ConfigurableProductOption" type="create" auth="adminOauth" url="/V1/configurable-products/{sku}/options" method="POST"> <contentType>application/json</contentType> <object dataType="ConfigurableProductOption" key="option"> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/extension_attribute_configurable_product_options-meta.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/extension_attribute_configurable_product_options-meta.xml index 2f1db19a1fd64..4b12abd3053fe 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/extension_attribute_configurable_product_options-meta.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/extension_attribute_configurable_product_options-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateExtensionAttributeConfigProductOption" dataType="ExtensionAttributeConfigProductOption" type="create"> <contentType>application/json</contentType> <array key="configurable_product_options"> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/valueIndex-meta.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/valueIndex-meta.xml index 8d955fcc94431..e83faddf0e332 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/valueIndex-meta.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Metadata/valueIndex-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="ValueIndex" dataType="ValueIndex" type="create"> <field key="value_index">integer</field> </operation> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Page/AdminProductCreatePage.xml index 01a494afd10e0..7705b34f0af03 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Page/AdminProductCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> <section name="AdminProductFormConfigurationsSection"/> <section name="AdminCreateProductConfigurationsPanel"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml index dac7027b73951..4289638352990 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminChooseAffectedAttributeSetPopup"> <element name="confirm" type="button" selector="button[data-index='confirm_button']" timeout="30"/> </section> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml index e04dbf274d932..99e47baac37d5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreateProductConfigurationsPanel"> <element name="next" type="button" selector=".steps-wizard-navigation .action-next-step" timeout="30"/> <element name="createNewAttribute" type="button" selector=".select-attributes-actions button[title='Create New Attribute']" timeout="30"/> @@ -18,6 +18,7 @@ <element name="id" type="text" selector="//tr[contains(@data-repeat-index, '0')]/td[2]/div"/> <element name="selectAll" type="button" selector=".action-select-all"/> + <element name="selectAllByAttribute" type="button" selector="//div[@data-attribute-title='{{attr}}']//button[contains(@class, 'action-select-all')]" parameterized="true"/> <element name="createNewValue" type="input" selector=".action-create-new" timeout="30"/> <element name="attributeName" type="input" selector="li[data-attribute-option-title=''] .admin__field-create-new .admin__control-text"/> <element name="saveAttribute" type="button" selector="li[data-attribute-option-title=''] .action-save" timeout="30"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml index f42a1f4d8dbd7..44077888f8bc0 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml @@ -7,13 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewAttributePanel"> <element name="container" type="text" selector="#create_new_attribute"/> <element name="saveAttribute" type="button" selector="#save"/> <element name="newAttributeIFrame" type="iframe" selector="create_new_attribute_container"/> <element name="defaultLabel" type="input" selector="input[name='frontend_label[0]']"/> <element name="inputType" type="select" selector="select[name='frontend_input']" timeout="30"/> + <element name="valuesRequired" type="select" selector="select#is_required"/> <element name="addOption" type="button" selector="#add_new_option_button"/> <element name="isDefault" type="radio" selector="[data-role='options-container'] tr:nth-of-type({{row}}) input[name='default[]']" parameterized="true"/> <element name="optionAdminValue" type="input" selector="[data-role='options-container'] input[name='option[value][option_{{row}}][0]']" parameterized="true"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index 6b278237b6599..0de7bc00044c8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -7,8 +7,9 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormConfigurationsSection"> + <element name="sectionHeader" type="text" selector=".admin__collapsible-block-wrapper[data-index='configurable']"/> <element name="createConfigurations" type="button" selector="button[data-index='create_configurable_products_button']" timeout="30"/> <element name="currentVariationsRows" type="button" selector=".data-row"/> <element name="currentVariationsNameCells" type="textarea" selector=".admin__control-fields[data-index='name_container']"/> @@ -31,11 +32,6 @@ <section name="AdminConfigurableProductSelectAttributesSlideOut"> <element name="grid" type="button" selector=".admin__data-grid-wrap tbody"/> </section> - <section name="AdminConfigurableProductAssignSourcesSlideOut"> - <element name="done" type="button" selector=".product_form_product_form_assign_sources_configurable_modal .action-primary" timeout="5"/> - <element name="assignSources" type="button" selector="(//button/span[contains(text(), 'Assign Sources')])[2]" timeout="5"/> - <element name="quantityPerSource" type="input" selector="input[name='quantity_resolver[dynamicRows][dynamicRows][0][quantity_per_source]']"/> - </section> <section name="StorefrontConfigurableProductPage"> <element name="productAttributeDropDown" type="select" selector="select[id*='attribute']"/> </section> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductGridActionSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductGridActionSection.xml index 1d4ec9270ef8f..e3403ce71acaa 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductGridActionSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductGridActionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridActionSection"> <element name="addConfigurableProduct" type="button" selector=".item[data-ui-id='products-list-add-new-product-button-item-configurable']" timeout="30"/> </section> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml new file mode 100644 index 0000000000000..b3077d9d5d566 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="CatalogProductsSection"> + <element name="catalogItem" type="button" selector="//*[@id='menu-magento-catalog-catalog']/a/span"/> + <element name="productItem" type="button" selector="//*[@data-ui-id='menu-magento-catalog-catalog-products']/a"/> + <element name="storesItem" type="button" selector="//*[@id='menu-magento-backend-stores']/a/span"/> + <element name="searchDefaultLabelField" type="input" selector="//*[@id='attributeGrid_filter_frontend_label']"/> + <element name="searchButton" type="button" selector="//div[@class='admin__filter-actions']//*[contains(text(), 'Search')]"/> + <element name="storesProductItem" type="button" selector="//*[@data-ui-id='menu-magento-catalog-catalog-attributes-attributes']/a"/> + <element name="createdAttributeItem" type="button" selector="//td[contains(@class, 'col-label') and normalize-space()='design']"/> + <element name="deleteAttributeItem" type="button" selector="//*[@id='delete']"/> + <element name="okButton" type="button" selector="//footer[@class='modal-footer']//*[contains(text(),'OK')]"/> + <element name="messageSuccessSavedProduct" type="button" selector="//div[@data-ui-id='messages-message-success']"/> + <element name="resetFilter" type="button" selector="//span[contains(text(), 'Reset Filter')]"/> + </section> + + <section name="ConfigurableProductSection"> + <element name="addProductItem" type="button" selector="//*[@id='add_new_product']/button[2]"/> + <element name="configProductItem" type="button" selector="//*[@id='add_new_product']//*[contains(text(),'Configurable Product')]"/> + <element name="nextButton" type="button" selector="//div[@class='nav-bar-outer-actions']//*[contains(text(),'Next')]"/> + <element name="generateConfigure" type="button" selector="//div[@class='nav-bar-outer-actions']//*[contains(text(),'Generate Products')]"/> + <element name="selectCreatedAttribute" type="button" selector="//*[@class='admin__data-grid-wrap']//td[normalize-space()='design']/preceding-sibling::td"/> + <element name="closeFrame" type="button" selector="//*[@class='modal-header']//*[contains(text(),'Create Product Configurations')]/following-sibling::button"/> + </section> + + <section name="NewProduct"> + <element name="productName" type="input" selector="//input[@name='product[name]']"/> + <element name="price" type="input" selector="//input[@name='product[price]']"/> + <element name="weight" type="input" selector="//input[@name='product[weight]']"/> + <element name="createConfigurationButton" type="button" selector="//*[contains(text(),'Create Configurations')]"/> + <element name="createNewAttributeButton" type="button" selector="//*[contains(text(),'Create New Attribute')]"/> + <element name="newAttributeIFrame" type="iframe" selector="create_new_attribute_container"/> + <element name="defaultLabel" type="input" selector="//*[@id='attribute_label']"/> + <element name="addOptionButton" type="button" selector="//*[@id='add_new_option_button']"/> + <element name="adminFieldRed" type="input" selector="//input[@name='option[value][option_0][0]']"/> + <element name="defaultStoreViewFieldRed" type="input" selector="//input[@name='option[value][option_0][1]']"/> + <element name="adminFieldBlue" type="input" selector="//input[@name='option[value][option_1][0]']"/> + <element name="defaultStoreViewFieldBlue" type="input" selector="//input[@name='option[value][option_1][1]']"/> + <element name="adminFieldYellow" type="input" selector="//input[@name='option[value][option_2][0]']"/> + <element name="defaultStoreViewFieldYellow" type="input" selector="//input[@name='option[value][option_2][1]']"/> + <element name="adminFieldGreen" type="input" selector="//input[@name='option[value][option_3][0]']"/> + <element name="defaultStoreViewFieldGreen" type="input" selector="//input[@name='option[value][option_3][1]']"/> + <element name="adminFieldBlack" type="input" selector="//input[@name='option[value][option_4][0]']"/> + <element name="defaultStoreViewFieldBlack" type="input" selector="//input[@name='option[value][option_4][1]']"/> + <element name="saveAttributeButton" type="button" selector="//*[@id='save']"/> + <element name="advancedAttributeProperties" type="button" selector="//*[@id='advanced_fieldset-wrapper']//*[contains(text(),'Advanced Attribute Properties')]"/> + <element name="attributeCodeField" type="input" selector="//*[@id='attribute_code']"/> + + </section> + + <section name="CreateProductConfigurations"> + <element name="checkboxRed" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'red')]/preceding-sibling::input"/> + <element name="checkboxBlue" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'blue')]/preceding-sibling::input"/> + <element name="checkboxYellow" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'yellow')]/preceding-sibling::input"/> + <element name="checkboxGreen" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'green')]/preceding-sibling::input"/> + <element name="checkboxBlack" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'black')]/preceding-sibling::input"/> + <element name="errorMessage" type="input" selector="//div[@data-ui-id='messages-message-error']"/> + </section> + +</sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 4f96ea41eac5a..b195c19f7bedd 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="optionByAttributeId" type="input" selector="#attribute{{var1}}" parameterized="true"/> <element name="productAttributeTitle1" type="text" selector="#product-options-wrapper div[tabindex='0'] label"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml index 7dbacfa2ce612..92928c9384672 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultImageConfigurableTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml index 17ace8419c034..48f46a1205ec3 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductCreateTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml index 17f17323a9edd..1a694b8adf17e 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductDeleteTest"> <annotations> <features value="ConfigurableProduct"/> @@ -16,6 +16,7 @@ <description value="admin should be able to delete a configurable product"/> <testCaseId value="MC-87"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> @@ -106,6 +107,7 @@ <description value="admin should be able to mass delete configurable products"/> <testCaseId value="MC-99"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml index c612431ec7044..5633c3675ca85 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductChildrenOutOfStockTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml index 77ccf7bc6900b..059a18200e90c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductSearchTest"> <annotations> <features value="ConfigurableProduct"/> @@ -16,6 +16,7 @@ <description value="admin should be able to search for a configurable product"/> <testCaseId value="MC-100"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> @@ -94,6 +95,7 @@ <description value="admin should be able to filter by type configurable product"/> <testCaseId value="MC-66"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSetEditContentTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSetEditContentTest.xml new file mode 100644 index 0000000000000..42e12852f563f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSetEditContentTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminConfigurableProductSetEditContentTest" extends="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Create/edit configurable product"/> + <title value="Admin should be able to set/edit product Content when editing a configurable product"/> + <description value="Admin should be able to set/edit product Content when editing a configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3424"/> + <group value="ConfigurableProduct"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <after> + <!-- Delete configurable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + + <!--Checking content storefront--> + <amOnPage url="{{BaseConfigurableProduct.name}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml index 2282da467a967..027c2ce729162 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductUpdateAttributeTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml index 154ce019f8c16..af12f49bf86ea 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigurableProductBulkUpdateTest"> <annotations> <features value="ConfigurableProduct"/> @@ -16,6 +16,7 @@ <description value="admin should be able to bulk update attributes of configurable products"/> <testCaseId value="MC-88"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> @@ -78,6 +79,7 @@ <description value="Admin should be able to remove a product configuration"/> <testCaseId value="MC-63"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> @@ -170,6 +172,7 @@ <description value="Admin should be able to disable a product configuration"/> <testCaseId value="MC-119"/> <group value="ConfigurableProduct"/> + <severity value="AVERAGE"/> </annotations> <before> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml new file mode 100644 index 0000000000000..11f1e9bb33c10 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableSetEditRelatedProductsTest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminConfigurableSetEditRelatedProductsTest" extends="AdminSimpleSetEditRelatedProductsTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Create/Edit configurable product"/> + <title value="Admin should be able to set/edit Related Products information when editing a configurable product"/> + <description value="Admin should be able to set/edit Related Products information when editing a configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3414"/> + <group value="ConfigurableProduct"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + </before> + <after> + <!-- Delete configurable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <!-- Create product --> + <remove keyForRemoval="goToCreateProduct"/> + <actionGroup ref="createConfigurableProduct" stepKey="fillProductForm"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + + <!--See related product in storefront--> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml index 4461c06ed6b51..aa34693ed82f0 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRelatedProductsTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml index 1c34566322c23..e7492f4eeaecf 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageConfigurableTest"> <annotations> <features value="ConfigurableProduct"/> @@ -109,9 +109,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml new file mode 100644 index 0000000000000..454f9f5f29a7a --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml @@ -0,0 +1,307 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchConfigurableByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search configurable product with product name"/> + <description value="Guest customer should be able to advance search configurable product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-138"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryHandle" before="simple1Handle"/> + + <createData entity="SimpleProduct" stepKey="simple1Handle" before="simple2Handle"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <createData entity="SimpleProduct" stepKey="simple2Handle" before="product"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <!-- TODO: Move configurable product creation to an actionGroup when MQE-697 is fixed --> + <createData entity="ApiConfigurableProductWithDescription" stepKey="product"/> + + <createData entity="productDropDownAttribute" stepKey="productAttributeHandle"/> + + <createData entity="productAttributeOption1" stepKey="productAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + <createData entity="productAttributeOption2" stepKey="productAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addToAttributeSetHandle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + + <createData entity="SimpleOne" stepKey="childProductHandle1"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + </createData> + <createData entity="SimpleOne" stepKey="childProductHandle2"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="configProductOptionHandle"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle2"/> + </createData> + </before> + <after> + <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchConfigurableBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search configurable product with product sku"/> + <description value="Guest customer should be able to advance search configurable product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-144"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryHandle" before="simple1Handle"/> + + <createData entity="SimpleProduct" stepKey="simple1Handle" before="simple2Handle"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <createData entity="SimpleProduct" stepKey="simple2Handle" before="product"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <!-- TODO: Move configurable product creation to an actionGroup when MQE-697 is fixed --> + <createData entity="ApiConfigurableProductWithDescription" stepKey="product"/> + + <createData entity="productDropDownAttribute" stepKey="productAttributeHandle"/> + + <createData entity="productAttributeOption1" stepKey="productAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + <createData entity="productAttributeOption2" stepKey="productAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addToAttributeSetHandle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + + <createData entity="SimpleOne" stepKey="childProductHandle1"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + </createData> + <createData entity="SimpleOne" stepKey="childProductHandle2"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="configProductOptionHandle"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle2"/> + </createData> + </before> + <after> + <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchConfigurableByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search configurable product with product description"/> + <description value="Guest customer should be able to advance search configurable product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-237"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryHandle" before="simple1Handle"/> + + <createData entity="SimpleProduct" stepKey="simple1Handle" before="simple2Handle"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <createData entity="SimpleProduct" stepKey="simple2Handle" before="product"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <!-- TODO: Move configurable product creation to an actionGroup when MQE-697 is fixed --> + <createData entity="ApiConfigurableProductWithDescription" stepKey="product"/> + + <createData entity="productDropDownAttribute" stepKey="productAttributeHandle"/> + + <createData entity="productAttributeOption1" stepKey="productAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + <createData entity="productAttributeOption2" stepKey="productAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addToAttributeSetHandle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + + <createData entity="SimpleOne" stepKey="childProductHandle1"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + </createData> + <createData entity="SimpleOne" stepKey="childProductHandle2"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="configProductOptionHandle"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle2"/> + </createData> + </before> + <after> + <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchConfigurableByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search configurable product with product short description"/> + <description value="Guest customer should be able to advance search configurable product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-240"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryHandle" before="simple1Handle"/> + + <createData entity="SimpleProduct" stepKey="simple1Handle" before="simple2Handle"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <createData entity="SimpleProduct" stepKey="simple2Handle" before="product"> + <requiredEntity createDataKey="categoryHandle"/> + </createData> + + <!-- TODO: Move configurable product creation to an actionGroup when MQE-697 is fixed --> + <createData entity="ApiConfigurableProductWithDescription" stepKey="product"/> + + <createData entity="productDropDownAttribute" stepKey="productAttributeHandle"/> + + <createData entity="productAttributeOption1" stepKey="productAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + <createData entity="productAttributeOption2" stepKey="productAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <createData entity="AddToDefaultSet" stepKey="addToAttributeSetHandle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </createData> + + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getAttributeOption1Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getAttributeOption2Handle"> + <requiredEntity createDataKey="productAttributeHandle"/> + </getData> + + <createData entity="SimpleOne" stepKey="childProductHandle1"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + </createData> + <createData entity="SimpleOne" stepKey="childProductHandle2"> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductTwoOptions" stepKey="configProductOptionHandle"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="productAttributeHandle"/> + <requiredEntity createDataKey="getAttributeOption1Handle"/> + <requiredEntity createDataKey="getAttributeOption2Handle"/> + </createData> + + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="configProductHandle2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="childProductHandle2"/> + </createData> + </before> + <after> + <deleteData createDataKey="simple1Handle" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2Handle" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductAttributeNameDesignTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductAttributeNameDesignTest.xml new file mode 100644 index 0000000000000..7fbff5eac2583 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductAttributeNameDesignTest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ConfigurableProductAttributeNameDesignTest"> + <annotations> + <title value="Generation of configurable products with an attribute named 'design'"/> + <description value="Generation of configurable products with an attribute named 'design'"/> + <features value="Product Customizable Option"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-93307"/> + <stories value="MAGETWO-91524 : 'element.disabled is not a function' error is thrown when configurable products are generated with an attribute named 'design'"/> + <group value="product"/> + </annotations> + + <before> + <!-- Log in to Dashboard page --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + </before> + + + <!-- Navigate to Catalog-> Products --> + <actionGroup ref="GotoCatalogProductsPage" stepKey="goToCatalogProductsPage"/> + <!-- Fill the fields on Configurable Product--> + <actionGroup ref="GotoConfigurableProductPage" stepKey="goToConfigurableProductPage"/> + <actionGroup ref="FillAllRequiredFields" stepKey="fillInAllRequiredFields"/> + <!-- Create New Attribute (Default Label= design) --> + <actionGroup ref="CreateNewAttribute" stepKey="createNewAttribute"/> + + <after> + <!-- Delete Created Attribute --> + <actionGroup ref="DeleteCreatedAttribute" stepKey="deleteCreatedAttribute"/> + </after> + + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml index 9467f82ebae24..b06067a6d43e4 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml @@ -7,15 +7,18 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ConfigurableProductPriceAdditionalStoreViewTest"> <annotations> <features value="ConfigurableProductPriceStoreFront"/> + <stories value="View products"/> <title value="Configurable product prices should not disappear on storefront for additional store"/> <description value="Configurable product price should not disappear for additional stores on frontEnd if disabled for default store"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-92247"/> - <group value="skip"/> + <skip> + <issueId value="MAGETWO-92247"/> + </skip> </annotations> <before> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml index a00ce52f442d7..6bbb97c66cdd8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <!--Create configurable product--> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageConfigurable" after="seeSimpleProductInGrid"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index 1f7b2bff0a7de..47ee09e4b2086 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <before> <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 07e2ecf86ca9e..6f9ad93a56dc5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <before> <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml new file mode 100644 index 0000000000000..eadc7dadaf708 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetConfigurableProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Configurable Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Configurable Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-120"/> + <group value="ConfigurableProduct"/> + <group value="WYSIWYGDisabled"/> + </annotations> + + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + </after> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Modify the Configurable Product that we created in the before block --> + <amOnPage url="{{AdminProductEditPage.url($$createConfigProduct.id$$)}}" stepKey="amOnEditPage"/> + <waitForPageLoad stepKey="waitForEditPage"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here, via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="$$createConfigProduct.name$$" stepKey="seeProductName"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml index c0e7c886d0cc1..231ef553d2d42 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontConfigurableProductChildSearchTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml index 3b0f5752ebf5d..836bc2cdca970 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontConfigurableProductBasicInfoTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest.xml index b8e894ccf3606..cc8291a83eb40 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontConfigurableProductGridViewTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml new file mode 100644 index 0000000000000..7cf352dcb61cd --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableProductWithFileCustomOptionTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Add configurable product to cart"/> + <title value="Correct error message and redirect with invalid file option"/> + <description value="Configurable product has file custom option. When adding to cart with an invalid filetype, the correct error message is shown, and options remain selected."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-93059"/> + <group value="ConfigurableProduct"/> + <group value="skip"/><!-- MAGETWO-94170 --> + </annotations> + + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create a configurable product via the UI --> + <actionGroup ref="createConfigurableProduct" stepKey="createProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + <!--Add custom option to configurable product--> + <actionGroup ref="AddProductCustomOptionFile" stepKey="addCustomOptionToProduct"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + + <!--Go to storefront--> + <amOnPage url="" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageLoad"/> + <click selector="{{StorefrontNavigationSection.topCategory($$createCategory.name$$)}}" stepKey="goToCategoryStorefront"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="$$createCategory.name$$" stepKey="seeOnCategoryPage"/> + <!--Add configurable product to cart--> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductTitleByName(BaseConfigurableProduct.name)}}" stepKey="hoverProductInGrid"/> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(BaseConfigurableProduct.name)}}" stepKey="tryAddToCartFromCategoryPage"/> + <waitForPageLoad stepKey="waitForRedirectToProductPage"/> + <seeInCurrentUrl url="{{StorefrontProductPage.url(BaseConfigurableProduct.urlKey)}}" stepKey="seeOnProductPage"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="{{colorProductAttribute2.name}}" stepKey="selectColor"/> + <!--Try invalid file--> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(ProductOptionFile.title)}}" userInput="lorem_ipsum.docx" stepKey="attachInvalidFile"/> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCartInvalidFile"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.alertMessage}}" stepKey="waitForErrorMessageInvalidFile"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="The file 'lorem_ipsum.docx' for '{{ProductOptionFile.title}}' has an invalid extension." stepKey="seeMessageInvalidFile"/> + <!--Option remains selected--> + <seeOptionIsSelected selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="{{colorProductAttribute2.name}}" stepKey="seeOptionRemainSelected"/> + <!--Try valid file--> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="attachValidFile"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$11.99" stepKey="seePriceUpdated"/> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCartValidFile"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.successMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{BaseConfigurableProduct.name}} to your shopping cart." stepKey="seeSuccessMessage"/> + + <!--Check item in cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <seeElement selector="{{CheckoutCartProductSection.ProductLinkByName(BaseConfigurableProduct.name)}}" stepKey="seeProductInCart"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, colorProductAttribute.default_label)}}" userInput="{{colorProductAttribute2.name}}" stepKey="seeSelectedOption"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="seeCorrectOptionFile"/> + <!--Delete cart item--> + <click selector="{{CheckoutCartProductSection.RemoveItem}}" stepKey="deleteCartItem"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json b/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json deleted file mode 100644 index 78063660f0025..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-product", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-product-video": "100.0.0-dev", - "magento/functional-test-module-configurable-sample-data": "100.0.0-dev", - "magento/functional-test-module-product-links-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Cart/Item/Renderer/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Cart/Item/Renderer/ConfigurableTest.php index e199841cbcdc4..9a21431ffcfa5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Cart/Item/Renderer/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Cart/Item/Renderer/ConfigurableTest.php @@ -11,154 +11,48 @@ class ConfigurableTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Framework\View\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_configManager; + private $configManager; /** @var \Magento\Catalog\Helper\Image|\PHPUnit_Framework_MockObject_MockObject */ - protected $_imageHelper; + private $imageHelper; /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_scopeConfig; + private $scopeConfig; /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $productConfigMock; + private $productConfigMock; /** @var Renderer */ - protected $_renderer; + private $renderer; protected function setUp() { parent::setUp(); $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_configManager = $this->createMock(\Magento\Framework\View\ConfigInterface::class); - $this->_imageHelper = $this->createPartialMock( + $this->configManager = $this->createMock(\Magento\Framework\View\ConfigInterface::class); + $this->imageHelper = $this->createPartialMock( \Magento\Catalog\Helper\Image::class, ['init', 'resize', '__toString'] ); - $this->_scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->productConfigMock = $this->createMock(\Magento\Catalog\Helper\Product\Configuration::class); - $this->_renderer = $objectManagerHelper->getObject( + $this->renderer = $objectManagerHelper->getObject( \Magento\ConfigurableProduct\Block\Cart\Item\Renderer\Configurable::class, [ - 'viewConfig' => $this->_configManager, - 'imageHelper' => $this->_imageHelper, - 'scopeConfig' => $this->_scopeConfig, + 'viewConfig' => $this->configManager, + 'imageHelper' => $this->imageHelper, + 'scopeConfig' => $this->scopeConfig, 'productConfig' => $this->productConfigMock ] ); } - /** - * Child thumbnail is available and config option is not set to use parent thumbnail. - */ - public function testGetProductForThumbnail() - { - $childHasThumbnail = true; - $useParentThumbnail = false; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['childProduct'], - $productForThumbnail, - 'Child product was expected to be returned.' - ); - } - - /** - * Child thumbnail is not available and config option is not set to use parent thumbnail. - */ - public function testGetProductForThumbnailChildThumbnailNotAvailable() - { - $childHasThumbnail = false; - $useParentThumbnail = false; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['parentProduct'], - $productForThumbnail, - 'Parent product was expected to be returned.' - ); - } - - /** - * Child thumbnail is available and config option is set to use parent thumbnail. - */ - public function testGetProductForThumbnailConfigUseParent() - { - $childHasThumbnail = true; - $useParentThumbnail = true; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['parentProduct'], - $productForThumbnail, - 'Parent product was expected to be returned ' . - 'if "checkout/cart/configurable_product_image option" is set to "parent" in system config.' - ); - } - - /** - * Initialize parent configurable product and child product. - * - * @param bool $childHasThumbnail - * @param bool $useParentThumbnail - * @return \Magento\Catalog\Model\Product[]|\PHPUnit_Framework_MockObject_MockObject[] - */ - protected function _initProducts($childHasThumbnail = true, $useParentThumbnail = false) - { - /** Set option which can force usage of parent product thumbnail when configurable product is displayed */ - $thumbnailToBeUsed = $useParentThumbnail - ? ThumbnailSource::OPTION_USE_PARENT_IMAGE - : ThumbnailSource::OPTION_USE_OWN_IMAGE; - $this->_scopeConfig->expects( - $this->any() - )->method( - 'getValue' - )->with( - Renderer::CONFIG_THUMBNAIL_SOURCE - )->will( - $this->returnValue($thumbnailToBeUsed) - ); - - /** Initialized parent product */ - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $parentProduct */ - $parentProduct = $this->createMock(\Magento\Catalog\Model\Product::class); - - /** Initialize child product */ - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $childProduct */ - $childProduct = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['getThumbnail', '__wakeup']); - $childThumbnail = $childHasThumbnail ? 'thumbnail.jpg' : 'no_selection'; - $childProduct->expects($this->any())->method('getThumbnail')->will($this->returnValue($childThumbnail)); - - /** Mock methods which return parent and child products */ - /** @var \Magento\Quote\Model\Quote\Item\Option|\PHPUnit_Framework_MockObject_MockObject $itemOption */ - $itemOption = $this->createMock(\Magento\Quote\Model\Quote\Item\Option::class); - $itemOption->expects($this->any())->method('getProduct')->will($this->returnValue($childProduct)); - /** @var \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject $item */ - $item = $this->createMock(\Magento\Quote\Model\Quote\Item::class); - $item->expects($this->any())->method('getProduct')->will($this->returnValue($parentProduct)); - $item->expects( - $this->any() - )->method( - 'getOptionByCode' - )->with( - 'simple_product' - )->will( - $this->returnValue($itemOption) - ); - $this->_renderer->setItem($item); - - return ['parentProduct' => $parentProduct, 'childProduct' => $childProduct]; - } - public function testGetOptionList() { $itemMock = $this->createMock(\Magento\Quote\Model\Quote\Item::class); - $this->_renderer->setItem($itemMock); + $this->renderer->setItem($itemMock); $this->productConfigMock->expects($this->once())->method('getOptions')->with($itemMock); - $this->_renderer->getOptionList(); + $this->renderer->getOptionList(); } public function testGetIdentities() @@ -168,7 +62,7 @@ public function testGetIdentities() $product->expects($this->exactly(2))->method('getIdentities')->will($this->returnValue($productTags)); $item = $this->createMock(\Magento\Quote\Model\Quote\Item::class); $item->expects($this->exactly(2))->method('getProduct')->will($this->returnValue($product)); - $this->_renderer->setItem($item); - $this->assertEquals(array_merge($productTags, $productTags), $this->_renderer->getIdentities()); + $this->renderer->setItem($item); + $this->assertEquals(array_merge($productTags, $productTags), $this->renderer->getIdentities()); } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php index b45306d670bff..25d8412c91056 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ConfigurableProduct\Test\Unit\Block\Product\View\Type; use Magento\Customer\Model\Session; @@ -83,6 +84,9 @@ class ConfigurableTest extends \PHPUnit\Framework\TestCase */ private $variationPricesMock; + /** + * {@inheritDoc} + */ protected function setUp() { $this->mockContextObject(); @@ -174,7 +178,7 @@ protected function setUp() * * @return array */ - public function cacheKeyProvider() : array + public function cacheKeyProvider(): array { return [ 'without_currency_and_customer_group' => [ @@ -313,11 +317,7 @@ public function testGetJsonConfig() $this->localeFormat->expects($this->atLeastOnce())->method('getPriceFormat')->willReturn([]); $this->localeFormat->expects($this->any()) ->method('getNumber') - ->willReturnMap([ - [$amount, $amount], - [$priceQty, $priceQty], - [$percentage, $percentage], - ]); + ->willReturnArgument(0); $this->variationPricesMock->expects($this->once()) ->method('getFormattedPrices') @@ -349,13 +349,13 @@ public function testGetJsonConfig() /** * Retrieve array with expected parameters for method getJsonConfig() * - * @param $productId - * @param $amount - * @param $priceQty - * @param $percentage + * @param int $productId + * @param double $amount + * @param int $priceQty + * @param int $percentage * @return array */ - private function getExpectedArray($productId, $amount, $priceQty, $percentage) + private function getExpectedArray($productId, $amount, $priceQty, $percentage): array { $expectedArray = [ 'attributes' => [], diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php index 99c31420473f5..189730e18080c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php @@ -55,24 +55,31 @@ protected function setUp() * situation: one product is supplying the price, which could be a price of zero (0) * * @dataProvider resolvePriceDataProvider + * + * @param $variantPrices + * @param $expectedPrice */ - public function testResolvePrice($expectedValue) + public function testResolvePrice($variantPrices, $expectedPrice) { - $price = $expectedValue; - $product = $this->getMockBuilder( \Magento\Catalog\Model\Product::class )->disableOriginalConstructor()->getMock(); $product->expects($this->never())->method('getSku'); - $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([$product]); - $this->priceResolver->expects($this->once()) + $products = array_map(function () { + return $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + }, $variantPrices); + + $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn($products); + $this->priceResolver ->method('resolvePrice') - ->with($product) - ->willReturn($price); + ->willReturnOnConsecutiveCalls(...$variantPrices); - $this->assertEquals($expectedValue, $this->resolver->resolvePrice($product)); + $actualPrice = $this->resolver->resolvePrice($product); + self::assertSame($expectedPrice, $actualPrice); } /** @@ -81,8 +88,40 @@ public function testResolvePrice($expectedValue) public function resolvePriceDataProvider() { return [ - 'price of zero' => [0.00], - 'price of five' => [5], + 'Single variant at price 0.00 (float), should return 0.00 (float)' => [ + $variantPrices = [ + 0.00, + ], + $expectedPrice = 0.00, + ], + 'Single variant at price 5 (integer), should return 5.00 (float)' => [ + $variantPrices = [ + 5, + ], + $expectedPrice = 5.00, + ], + 'Single variants at price null (null), should return 0.00 (float)' => [ + $variantPrices = [ + null, + ], + $expectedPrice = 0.00, + ], + 'Multiple variants at price 0, 10, 20, should return 0.00 (float)' => [ + $variantPrices = [ + 0, + 10, + 20, + ], + $expectedPrice = 0.00, + ], + 'Multiple variants at price 10, 0, 20, should return 0.00 (float)' => [ + $variantPrices = [ + 10, + 0, + 20, + ], + $expectedPrice = 0.00, + ], ]; } } diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 959c036981878..b03a8a65702be 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -14,12 +14,12 @@ "magento/module-customer": "*", "magento/module-eav": "*", "magento/module-media-storage": "*", - "magento/module-msrp": "*", "magento/module-quote": "*", "magento/module-store": "*", "magento/module-ui": "*" }, "suggest": { + "magento/module-msrp": "*", "magento/module-webapi": "*", "magento/module-sales": "*", "magento/module-product-video": "*", diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/system.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/system.xml index ba52b51d6b077..86baea3c0d296 100644 --- a/app/code/Magento/ConfigurableProduct/etc/adminhtml/system.xml +++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="checkout"> <group id="cart"> - <field id="configurable_product_image" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="configurable_product_image" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Configurable Product Image</label> <source_model>Magento\Catalog\Model\Config\Source\Product\Thumbnail</source_model> </field> diff --git a/app/code/Magento/ConfigurableProduct/etc/db_schema_whitelist.json b/app/code/Magento/ConfigurableProduct/etc/db_schema_whitelist.json index c5c83dd31ca23..70ce362e02c71 100644 --- a/app/code/Magento/ConfigurableProduct/etc/db_schema_whitelist.json +++ b/app/code/Magento/ConfigurableProduct/etc/db_schema_whitelist.json @@ -1,50 +1,50 @@ { - "catalog_product_super_attribute": { - "column": { - "product_super_attribute_id": true, - "product_id": true, - "attribute_id": true, - "position": true + "catalog_product_super_attribute": { + "column": { + "product_super_attribute_id": true, + "product_id": true, + "attribute_id": true, + "position": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_SPR_ATTR_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_SUPER_ATTRIBUTE_PRODUCT_ID_ATTRIBUTE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_SPR_ATTR_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_SUPER_ATTRIBUTE_PRODUCT_ID_ATTRIBUTE_ID": true - } - }, - "catalog_product_super_attribute_label": { - "column": { - "value_id": true, - "product_super_attribute_id": true, - "store_id": true, - "use_default": true, - "value": true - }, - "index": { - "CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "FK_309442281DF7784210ED82B2CC51E5D5": true, - "CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE_ID_STORE_STORE_ID": true, - "CAT_PRD_SPR_ATTR_LBL_PRD_SPR_ATTR_ID_STORE_ID": true - } - }, - "catalog_product_super_link": { - "column": { - "link_id": true, - "product_id": true, - "parent_id": true - }, - "index": { - "CATALOG_PRODUCT_SUPER_LINK_PARENT_ID": true + "catalog_product_super_attribute_label": { + "column": { + "value_id": true, + "product_super_attribute_id": true, + "store_id": true, + "use_default": true, + "value": true + }, + "index": { + "CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "FK_309442281DF7784210ED82B2CC51E5D5": true, + "CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL_STORE_ID_STORE_STORE_ID": true, + "CAT_PRD_SPR_ATTR_LBL_PRD_SPR_ATTR_ID_STORE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "CAT_PRD_SPR_LNK_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CAT_PRD_SPR_LNK_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true, - "CATALOG_PRODUCT_SUPER_LINK_PRODUCT_ID_PARENT_ID": true, - "CAT_PRD_SPR_LNK_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + "catalog_product_super_link": { + "column": { + "link_id": true, + "product_id": true, + "parent_id": true + }, + "index": { + "CATALOG_PRODUCT_SUPER_LINK_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CAT_PRD_SPR_LNK_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CAT_PRD_SPR_LNK_PARENT_ID_CAT_PRD_ENTT_ENTT_ID": true, + "CATALOG_PRODUCT_SUPER_LINK_PRODUCT_ID_PARENT_ID": true, + "CAT_PRD_SPR_LNK_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 15dbc53a5447a..a16feacc4f993 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -176,6 +176,14 @@ <argument name="batchSizeAdjusters" xsi:type="array"> <item name="configurable" xsi:type="object">Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\CompositeProductBatchSizeAdjuster</item> </argument> + <!-- + real batch size will be smaller. + It depends on amount configurable product variations. + E.g for 100 variations real batch size will be 50000/100=500 + --> + <argument name="batchRowsCount" xsi:type="array"> + <item name="configurable" xsi:type="number">50000</item> + </argument> </arguments> </type> <type name="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable"> @@ -205,4 +213,11 @@ <type name="Magento\Catalog\Model\Product"> <plugin name="product_identities_extender" type="Magento\ConfigurableProduct\Model\Plugin\ProductIdentitiesExtender" /> </type> + <type name="Magento\Catalog\Model\Product\Configuration\Item\ItemResolverComposite"> + <arguments> + <argument name="itemResolvers" xsi:type="array"> + <item name="configurable" xsi:type="string">Magento\ConfigurableProduct\Model\Product\Configuration\Item\ItemProductResolver</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index 592b0292c98ab..bb830c36b929d 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -7,13 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\Checkout\CustomerData\ItemPoolInterface"> - <arguments> - <argument name="itemMap" xsi:type="array"> - <item name="configurable" xsi:type="string">Magento\ConfigurableProduct\CustomerData\ConfigurableItem</item> - </argument> - </arguments> - </type> <type name="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface"> <plugin name="Magento_ConfigurableProduct_Plugin_Model_ResourceModel_Attribute_InStockOptionSelectBuilder" type="Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Attribute\InStockOptionSelectBuilder"/> </type> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index 9aa67beb3a51d..e7c8aa6f71745 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -357,12 +357,12 @@ define([ var element; _.each(this.disabledAttributes, function (attribute) { - registry.get('index = ' + attribute).disabled(false); + registry.get('inputName = ' + 'product[' + attribute + ']').disabled(false); }); this.disabledAttributes = []; _.each(attributes, function (attribute) { - element = registry.get('index = ' + attribute.code); + element = registry.get('inputName = ' + 'product[' + attribute.code + ']'); if (!_.isUndefined(element)) { element.disabled(true); diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml b/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml index 9b8e2c0c8c0bd..f5ed067967547 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml +++ b/app/code/Magento/ConfigurableProduct/view/frontend/templates/product/view/type/options/configurable.phtml @@ -38,6 +38,9 @@ $_attributes = $block->decorateArray($block->getAllowAttributes()); "gallerySwitchStrategy": "<?php /* @escapeNotVerified */ echo $block->getVar('gallery_switch_strategy', 'Magento_ConfigurableProduct') ?: 'replace'; ?>" } + }, + "*" : { + "Magento_ConfigurableProduct/js/catalog-add-to-cart": {} } } </script> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/catalog-add-to-cart.js new file mode 100644 index 0000000000000..3e6a611c268af --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/catalog-add-to-cart.js @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +require([ + 'jquery' +], function ($) { + 'use strict'; + + /** + * Add selected configurable attributes to redirect url + * + * @see Magento_Catalog/js/catalog-add-to-cart + */ + $('body').on('catalogCategoryAddToCartRedirect', function (event, data) { + $(data.form).find('select[name*="super"]').each(function (index, item) { + data.redirectParameters.push(item.config.id + '=' + $(item).val()); + }); + }); +}); diff --git a/app/code/Magento/ConfigurableProductGraphQl/Test/Mftf/composer.json b/app/code/Magento/ConfigurableProductGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 6d32ef35f6eb2..0000000000000 --- a/app/code/Magento/ConfigurableProductGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-product-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index 42d7d91fb90e8..8ef1e24125981 100644 --- a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php +++ b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php @@ -5,11 +5,12 @@ */ namespace Magento\ConfigurableProductSales\Model\Order\Reorder; -use Magento\Sales\Model\Order\Reorder\OrderedProductAvailabilityCheckerInterface; -use Magento\Sales\Model\Order\Item; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Sales\Model\Order\Item; +use Magento\Sales\Model\Order\Reorder\OrderedProductAvailabilityCheckerInterface; use Magento\Store\Model\Store; /** @@ -27,16 +28,24 @@ class OrderedProductAvailabilityChecker implements OrderedProductAvailabilityChe */ private $metadataPool; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @param ResourceConnection $resourceConnection * @param MetadataPool $metadataPool + * @param ProductRepositoryInterface $productRepository */ public function __construct( ResourceConnection $resourceConnection, - MetadataPool $metadataPool + MetadataPool $metadataPool, + ProductRepositoryInterface $productRepository ) { $this->resourceConnection = $resourceConnection; $this->metadataPool = $metadataPool; + $this->productRepository = $productRepository; } /** @@ -48,7 +57,9 @@ public function isAvailable(Item $item) $superAttribute = $buyRequest->getData()['super_attribute'] ?? []; $connection = $this->getConnection(); $select = $connection->select(); - $orderItemParentId = $item->getParentItem()->getProductId(); + $linkField = $this->getMetadata()->getLinkField(); + $parentItem = $this->productRepository->getById($item->getParentItem()->getProductId()); + $orderItemParentId = $parentItem->getData($linkField); $select->from( ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')], ['cpe.entity_id'] @@ -67,7 +78,7 @@ public function isAvailable(Item $item) ['cpid' . $attributeId => $this->resourceConnection->getTableName('catalog_product_entity_int')], sprintf( 'cpe.%1$s = cpid%2$d.%1$s AND cpid%2$d.attribute_id = %2$d AND cpid%2$d.store_id = %3$d', - $this->getMetadata()->getLinkField(), + $linkField, $attributeId, Store::DEFAULT_STORE_ID ), @@ -77,7 +88,7 @@ public function isAvailable(Item $item) ['cpis' . $attributeId => $this->resourceConnection->getTableName('catalog_product_entity_int')], sprintf( 'cpe.%1$s = cpis%2$d.%1$s AND cpis%2$d.attribute_id = %2$d AND cpis%2$d.store_id = %3$d', - $this->getMetadata()->getLinkField(), + $linkField, $attributeId, $item->getStoreId() ), diff --git a/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json b/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json deleted file mode 100644 index 416e6a0c45ce1..0000000000000 --- a/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-product-sales", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-configurable-product": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Contact/Controller/Index/Post.php b/app/code/Magento/Contact/Controller/Index/Post.php index b51e3c9189502..bdc2fee088d9e 100644 --- a/app/code/Magento/Contact/Controller/Index/Post.php +++ b/app/code/Magento/Contact/Controller/Index/Post.php @@ -13,7 +13,6 @@ use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\HTTP\PhpEnvironment\Request; use Psr\Log\LoggerInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; @@ -68,7 +67,7 @@ public function __construct( */ public function execute() { - if (!$this->isPostRequest()) { + if (!$this->getRequest()->isPost()) { return $this->resultRedirectFactory->create()->setPath('*/*/'); } try { @@ -102,16 +101,6 @@ private function sendEmail($post) ); } - /** - * @return bool - */ - private function isPostRequest() - { - /** @var Request $request */ - $request = $this->getRequest(); - return !empty($request->getPostValue()); - } - /** * @return array * @throws \Exception diff --git a/app/code/Magento/Contact/Test/Mftf/composer.json b/app/code/Magento/Contact/Test/Mftf/composer.json deleted file mode 100644 index 824257ae8d0c4..0000000000000 --- a/app/code/Magento/Contact/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-contact", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Contact/Test/Unit/Controller/Index/PostTest.php b/app/code/Magento/Contact/Test/Unit/Controller/Index/PostTest.php index 4b49373201e51..e5b7f53ecb26b 100644 --- a/app/code/Magento/Contact/Test/Unit/Controller/Index/PostTest.php +++ b/app/code/Magento/Contact/Test/Unit/Controller/Index/PostTest.php @@ -65,6 +65,9 @@ class PostTest extends \PHPUnit\Framework\TestCase */ private $mailMock; + /** + * test setup + */ protected function setUp() { $this->mailMock = $this->getMockBuilder(MailInterface::class)->getMockForAbstractClass(); @@ -78,7 +81,7 @@ protected function setUp() $this->createMock(\Magento\Framework\Message\ManagerInterface::class); $this->requestStub = $this->createPartialMock( \Magento\Framework\App\Request\Http::class, - ['getPostValue', 'getParams', 'getParam'] + ['getPostValue', 'getParams', 'getParam', 'isPost'] ); $this->redirectResultMock = $this->createMock(\Magento\Framework\Controller\Result\Redirect::class); $this->redirectResultMock->method('setPath')->willReturnSelf(); @@ -120,6 +123,9 @@ protected function setUp() ); } + /** + * testExecuteEmptyPost + */ public function testExecuteEmptyPost() { $this->stubRequestPostData([]); @@ -127,6 +133,8 @@ public function testExecuteEmptyPost() } /** + * @param array $postData + * @param bool $exceptionExpected * @dataProvider postDataProvider */ public function testExecutePostValidation($postData, $exceptionExpected) @@ -159,6 +167,9 @@ public function postDataProvider() ]; } + /** + * testExecuteValidPost + */ public function testExecuteValidPost() { $post = ['name' => 'Name', 'comment' => 'Comment', 'email' => 'valid@mail.com', 'hideit' => null]; @@ -177,6 +188,10 @@ public function testExecuteValidPost() */ private function stubRequestPostData($post) { + $this->requestStub + ->expects($this->once()) + ->method('isPost') + ->willReturn(!empty($post)); $this->requestStub->method('getPostValue')->willReturn($post); $this->requestStub->method('getParams')->willReturn($post); $this->requestStub->method('getParam')->willReturnCallback( diff --git a/app/code/Magento/Cookie/Test/Mftf/composer.json b/app/code/Magento/Cookie/Test/Mftf/composer.json deleted file mode 100644 index 54578425c5516..0000000000000 --- a/app/code/Magento/Cookie/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-cookie", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cookie/etc/adminhtml/system.xml b/app/code/Magento/Cookie/etc/adminhtml/system.xml index 26c963ddba76d..9790410969055 100644 --- a/app/code/Magento/Cookie/etc/adminhtml/system.xml +++ b/app/code/Magento/Cookie/etc/adminhtml/system.xml @@ -22,7 +22,7 @@ <label>Cookie Domain</label> <backend_model>Magento\Cookie\Model\Config\Backend\Domain</backend_model> </field> - <field id="cookie_httponly" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="cookie_httponly" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Use HTTP Only</label> <comment> <![CDATA[<strong style="color:red">Warning</strong>: Do not set to "No". User security could be compromised.]]> diff --git a/app/code/Magento/Cookie/view/frontend/web/js/notices.js b/app/code/Magento/Cookie/view/frontend/web/js/notices.js index 253950747ce14..f1f3754ea54b1 100644 --- a/app/code/Magento/Cookie/view/frontend/web/js/notices.js +++ b/app/code/Magento/Cookie/view/frontend/web/js/notices.js @@ -29,7 +29,7 @@ define([ }); if ($.mage.cookies.get(this.options.cookieName)) { - window.location.reload(); + this.element.hide(); } else { window.location.href = this.options.noCookiesUrl; } diff --git a/app/code/Magento/Cron/Console/Command/CronCommand.php b/app/code/Magento/Cron/Console/Command/CronCommand.php index 78bbb2329f8dc..142a9a397eb5f 100644 --- a/app/code/Magento/Cron/Console/Command/CronCommand.php +++ b/app/code/Magento/Cron/Console/Command/CronCommand.php @@ -10,10 +10,12 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputOption; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ObjectManagerFactory; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManager; use Magento\Cron\Observer\ProcessCronQueueObserver; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Console\Cli; use Magento\Framework\Shell\ComplexParameter; @@ -35,13 +37,24 @@ class CronCommand extends Command private $objectManagerFactory; /** - * Constructor + * Application deployment configuration * + * @var DeploymentConfig + */ + private $deploymentConfig; + + /** * @param ObjectManagerFactory $objectManagerFactory + * @param DeploymentConfig $deploymentConfig Application deployment configuration */ - public function __construct(ObjectManagerFactory $objectManagerFactory) - { + public function __construct( + ObjectManagerFactory $objectManagerFactory, + DeploymentConfig $deploymentConfig = null + ) { $this->objectManagerFactory = $objectManagerFactory; + $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get( + DeploymentConfig::class + ); parent::__construct(); } @@ -71,10 +84,16 @@ protected function configure() } /** + * Runs cron jobs if cron is not disabled in Magento configurations + * * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { + if (!$this->deploymentConfig->get('cron/enabled', 1)) { + $output->writeln('<info>' . 'Cron is disabled. Jobs were not run.' . '</info>'); + return; + } $omParams = $_SERVER; $omParams[StoreManager::PARAM_RUN_CODE] = 'admin'; $omParams[Store::CUSTOM_ENTRY_POINT_PARAM] = true; diff --git a/app/code/Magento/Cron/Test/Mftf/composer.json b/app/code/Magento/Cron/Test/Mftf/composer.json deleted file mode 100644 index a091882117d12..0000000000000 --- a/app/code/Magento/Cron/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-cron", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cron/Test/Unit/Console/Command/CronCommandTest.php b/app/code/Magento/Cron/Test/Unit/Console/Command/CronCommandTest.php index 8b3e50d6afb3a..6b1af9323cc93 100644 --- a/app/code/Magento/Cron/Test/Unit/Console/Command/CronCommandTest.php +++ b/app/code/Magento/Cron/Test/Unit/Console/Command/CronCommandTest.php @@ -6,19 +6,74 @@ namespace Magento\Cron\Test\Unit\Console\Command; use Magento\Cron\Console\Command\CronCommand; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManagerFactory; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use Symfony\Component\Console\Tester\CommandTester; class CronCommandTest extends \PHPUnit\Framework\TestCase { + /** + * @var ObjectManagerFactory|MockObject + */ + private $objectManagerFactory; + + /** + * @var DeploymentConfig|MockObject + */ + private $deploymentConfigMock; + + protected function setUp() + { + $this->objectManagerFactory = $this->createMock(ObjectManagerFactory::class); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + } + + /** + * Test command with disables cron + * + * @return void + */ + public function testExecuteWithDisabledCrons() + { + $this->objectManagerFactory->expects($this->never()) + ->method('create'); + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with('cron/enabled', 1) + ->willReturn(0); + $commandTester = new CommandTester( + new CronCommand($this->objectManagerFactory, $this->deploymentConfigMock) + ); + $commandTester->execute([]); + $expectedMsg = 'Cron is disabled. Jobs were not run.' . PHP_EOL; + $this->assertEquals($expectedMsg, $commandTester->getDisplay()); + } + + /** + * Test command with enabled cron + * + * @return void + */ public function testExecute() { - $objectManagerFactory = $this->createMock(\Magento\Framework\App\ObjectManagerFactory::class); $objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); $cron = $this->createMock(\Magento\Framework\App\Cron::class); - $objectManager->expects($this->once())->method('create')->willReturn($cron); - $cron->expects($this->once())->method('launch'); - $objectManagerFactory->expects($this->once())->method('create')->willReturn($objectManager); - $commandTester = new CommandTester(new CronCommand($objectManagerFactory)); + $objectManager->expects($this->once()) + ->method('create') + ->willReturn($cron); + $cron->expects($this->once()) + ->method('launch'); + $this->objectManagerFactory->expects($this->once()) + ->method('create') + ->willReturn($objectManager); + $this->deploymentConfigMock->expects($this->once()) + ->method('get') + ->with('cron/enabled', 1) + ->willReturn(1); + $commandTester = new CommandTester( + new CronCommand($this->objectManagerFactory, $this->deploymentConfigMock) + ); $commandTester->execute([]); $expectedMsg = 'Ran jobs by schedule.' . PHP_EOL; $this->assertEquals($expectedMsg, $commandTester->getDisplay()); diff --git a/app/code/Magento/Cron/etc/cron_groups.xml b/app/code/Magento/Cron/etc/cron_groups.xml index a01426eab723e..9aa57662427c8 100644 --- a/app/code/Magento/Cron/etc/cron_groups.xml +++ b/app/code/Magento/Cron/etc/cron_groups.xml @@ -11,8 +11,8 @@ <schedule_ahead_for>20</schedule_ahead_for> <schedule_lifetime>15</schedule_lifetime> <history_cleanup_every>10</history_cleanup_every> - <history_success_lifetime>10080</history_success_lifetime> - <history_failure_lifetime>10080</history_failure_lifetime> + <history_success_lifetime>60</history_success_lifetime> + <history_failure_lifetime>4320</history_failure_lifetime> <use_separate_process>0</use_separate_process> </group> </config> diff --git a/app/code/Magento/Cron/etc/db_schema_whitelist.json b/app/code/Magento/Cron/etc/db_schema_whitelist.json index 7c98f1d69ba59..c8666896627e2 100644 --- a/app/code/Magento/Cron/etc/db_schema_whitelist.json +++ b/app/code/Magento/Cron/etc/db_schema_whitelist.json @@ -1,21 +1,21 @@ { - "cron_schedule": { - "column": { - "schedule_id": true, - "job_code": true, - "status": true, - "messages": true, - "created_at": true, - "scheduled_at": true, - "executed_at": true, - "finished_at": true - }, - "index": { - "CRON_SCHEDULE_JOB_CODE": true, - "CRON_SCHEDULE_SCHEDULED_AT_STATUS": true - }, - "constraint": { - "PRIMARY": true + "cron_schedule": { + "column": { + "schedule_id": true, + "job_code": true, + "status": true, + "messages": true, + "created_at": true, + "scheduled_at": true, + "executed_at": true, + "finished_at": true + }, + "index": { + "CRON_SCHEDULE_JOB_CODE": true, + "CRON_SCHEDULE_SCHEDULED_AT_STATUS": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json b/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json deleted file mode 100644 index ca14f3ebd0bf9..0000000000000 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-currency-symbol", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Customer/Block/Account/Dashboard/Info.php b/app/code/Magento/Customer/Block/Account/Dashboard/Info.php index ded7238edc755..87132c3afb8bc 100644 --- a/app/code/Magento/Customer/Block/Account/Dashboard/Info.php +++ b/app/code/Magento/Customer/Block/Account/Dashboard/Info.php @@ -102,7 +102,7 @@ public function getSubscriptionObject() $this->_subscription = $this->_createSubscriber(); $customer = $this->getCustomer(); if ($customer) { - $this->_subscription->loadByEmail($customer->getEmail()); + $this->_subscription->loadByCustomerId($customer->getId()); } } return $this->_subscription; diff --git a/app/code/Magento/Customer/Block/Account/Navigation.php b/app/code/Magento/Customer/Block/Account/Navigation.php index 64ced9d592e11..705acbcda4c6a 100644 --- a/app/code/Magento/Customer/Block/Account/Navigation.php +++ b/app/code/Magento/Customer/Block/Account/Navigation.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Block\Account; @@ -44,12 +45,8 @@ public function getLinks() * @return int * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function compare(SortLinkInterface $firstLink, SortLinkInterface $secondLink) + private function compare(SortLinkInterface $firstLink, SortLinkInterface $secondLink): int { - if ($firstLink->getSortOrder() == $secondLink->getSortOrder()) { - return 0; - } - - return ($firstLink->getSortOrder() < $secondLink->getSortOrder()) ? 1 : -1; + return $secondLink->getSortOrder() <=> $firstLink->getSortOrder(); } } diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php index 9ee152078d960..db560f7de3ecb 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php @@ -82,7 +82,7 @@ protected function _construct() parent::_construct(); $this->setUseAjax(true); $this->_parentTemplate = $this->getTemplate(); - $this->setTemplate('tab/cart.phtml'); + $this->setTemplate('Magento_Customer::tab/cart.phtml'); } /** @@ -220,7 +220,7 @@ public function getGridParentHtml() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRowUrl($row) { diff --git a/app/code/Magento/Customer/Block/Adminhtml/System/Config/Validatevat.php b/app/code/Magento/Customer/Block/Adminhtml/System/Config/Validatevat.php index 83ce7bef26d60..8cbe5c0680bd3 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/System/Config/Validatevat.php +++ b/app/code/Magento/Customer/Block/Adminhtml/System/Config/Validatevat.php @@ -99,7 +99,7 @@ protected function _prepareLayout() { parent::_prepareLayout(); if (!$this->getTemplate()) { - $this->setTemplate('system/config/validatevat.phtml'); + $this->setTemplate('Magento_Customer::system/config/validatevat.phtml'); } return $this; } diff --git a/app/code/Magento/Customer/Block/DataProviders/AddressAttributeData.php b/app/code/Magento/Customer/Block/DataProviders/AddressAttributeData.php new file mode 100644 index 0000000000000..2be340c8ccca4 --- /dev/null +++ b/app/code/Magento/Customer/Block/DataProviders/AddressAttributeData.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Block\DataProviders; + +use Magento\Framework\Escaper; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Provides address attribute data into template. + */ +class AddressAttributeData implements ArgumentInterface +{ + /** + * @var AddressMetadataInterface + */ + private $addressMetadata; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @param AddressMetadataInterface $addressMetadata + * @param Escaper $escaper + */ + public function __construct( + AddressMetadataInterface $addressMetadata, + Escaper $escaper + ) { + + $this->addressMetadata = $addressMetadata; + $this->escaper = $escaper; + } + + /** + * Returns frontend label for attribute. + * + * @param string $attributeCode + * @return string + * @throws LocalizedException + */ + public function getFrontendLabel(string $attributeCode): string + { + try { + $attribute = $this->addressMetadata->getAttributeMetadata($attributeCode); + $frontendLabel = $attribute->getFrontendLabel(); + } catch (NoSuchEntityException $e) { + $frontendLabel = ''; + } + + return $this->escaper->escapeHtml(__($frontendLabel)); + } +} diff --git a/app/code/Magento/Customer/Block/Widget/Company.php b/app/code/Magento/Customer/Block/Widget/Company.php index 4b928805fa543..8052e396f7dda 100644 --- a/app/code/Magento/Customer/Block/Widget/Company.php +++ b/app/code/Magento/Customer/Block/Widget/Company.php @@ -40,12 +40,12 @@ class Company extends AbstractWidget protected $options; /** - * @param Context $context - * @param AddressHelper $addressHelper + * @param Context $context + * @param AddressHelper $addressHelper * @param CustomerMetadataInterface $customerMetadata - * @param Options $options - * @param AddressMetadataInterface $addressMetadata - * @param array $data + * @param Options $options + * @param AddressMetadataInterface $addressMetadata + * @param array $data */ public function __construct( Context $context, @@ -69,7 +69,7 @@ public function _construct() parent::_construct(); // default template location - $this->setTemplate('widget/company.phtml'); + $this->setTemplate('Magento_Customer::widget/company.phtml'); } /** @@ -95,7 +95,7 @@ public function showCompany() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _getAttribute($attributeCode) { diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php index 1a1d5d81bf13d..936563d519823 100644 --- a/app/code/Magento/Customer/Block/Widget/Dob.php +++ b/app/code/Magento/Customer/Block/Widget/Dob.php @@ -66,7 +66,7 @@ public function __construct( public function _construct() { parent::_construct(); - $this->setTemplate('widget/dob.phtml'); + $this->setTemplate('Magento_Customer::widget/dob.phtml'); } /** diff --git a/app/code/Magento/Customer/Block/Widget/Fax.php b/app/code/Magento/Customer/Block/Widget/Fax.php index 2ede51f0eec96..aa1cff632abd3 100644 --- a/app/code/Magento/Customer/Block/Widget/Fax.php +++ b/app/code/Magento/Customer/Block/Widget/Fax.php @@ -40,12 +40,12 @@ class Fax extends AbstractWidget protected $options; /** - * @param Context $context - * @param AddressHelper $addressHelper + * @param Context $context + * @param AddressHelper $addressHelper * @param CustomerMetadataInterface $customerMetadata - * @param Options $options - * @param AddressMetadataInterface $addressMetadata - * @param array $data + * @param Options $options + * @param AddressMetadataInterface $addressMetadata + * @param array $data */ public function __construct( Context $context, @@ -69,7 +69,7 @@ public function _construct() parent::_construct(); // default template location - $this->setTemplate('widget/fax.phtml'); + $this->setTemplate('Magento_Customer::widget/fax.phtml'); } /** @@ -95,7 +95,7 @@ public function showFax() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _getAttribute($attributeCode) { diff --git a/app/code/Magento/Customer/Block/Widget/Gender.php b/app/code/Magento/Customer/Block/Widget/Gender.php index 1d3730e6c6d58..d03c64a54fb94 100644 --- a/app/code/Magento/Customer/Block/Widget/Gender.php +++ b/app/code/Magento/Customer/Block/Widget/Gender.php @@ -59,7 +59,7 @@ public function __construct( public function _construct() { parent::_construct(); - $this->setTemplate('widget/gender.phtml'); + $this->setTemplate('Magento_Customer::widget/gender.phtml'); } /** diff --git a/app/code/Magento/Customer/Block/Widget/Name.php b/app/code/Magento/Customer/Block/Widget/Name.php index 35f3bbefb8f00..d50045f4a4092 100644 --- a/app/code/Magento/Customer/Block/Widget/Name.php +++ b/app/code/Magento/Customer/Block/Widget/Name.php @@ -62,7 +62,7 @@ public function _construct() parent::_construct(); // default template location - $this->setTemplate('widget/name.phtml'); + $this->setTemplate('Magento_Customer::widget/name.phtml'); } /** @@ -201,7 +201,7 @@ public function getContainerClassName() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _getAttribute($attributeCode) { diff --git a/app/code/Magento/Customer/Block/Widget/Taxvat.php b/app/code/Magento/Customer/Block/Widget/Taxvat.php index e695681ad4a6c..e5c9c01dc3ac5 100644 --- a/app/code/Magento/Customer/Block/Widget/Taxvat.php +++ b/app/code/Magento/Customer/Block/Widget/Taxvat.php @@ -41,7 +41,7 @@ public function __construct( public function _construct() { parent::_construct(); - $this->setTemplate('widget/taxvat.phtml'); + $this->setTemplate('Magento_Customer::widget/taxvat.phtml'); } /** diff --git a/app/code/Magento/Customer/Block/Widget/Telephone.php b/app/code/Magento/Customer/Block/Widget/Telephone.php index b0d155eff6db5..b67e92ed7d29f 100644 --- a/app/code/Magento/Customer/Block/Widget/Telephone.php +++ b/app/code/Magento/Customer/Block/Widget/Telephone.php @@ -40,12 +40,12 @@ class Telephone extends AbstractWidget protected $options; /** - * @param Context $context - * @param AddressHelper $addressHelper + * @param Context $context + * @param AddressHelper $addressHelper * @param CustomerMetadataInterface $customerMetadata - * @param Options $options - * @param AddressMetadataInterface $addressMetadata - * @param array $data + * @param Options $options + * @param AddressMetadataInterface $addressMetadata + * @param array $data */ public function __construct( Context $context, @@ -69,7 +69,7 @@ public function _construct() parent::_construct(); // default template location - $this->setTemplate('widget/telephone.phtml'); + $this->setTemplate('Magento_Customer::widget/telephone.phtml'); } /** @@ -95,7 +95,7 @@ public function showTelephone() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _getAttribute($attributeCode) { diff --git a/app/code/Magento/Customer/Controller/Account/CreatePost.php b/app/code/Magento/Customer/Controller/Account/CreatePost.php index 27d8ddd99344c..bb94063226f41 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePost.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePost.php @@ -11,8 +11,13 @@ use Magento\Framework\App\Action\Context; use Magento\Customer\Model\Session; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\CsrfAwareActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; use Magento\Store\Model\StoreManagerInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Helper\Address; @@ -29,12 +34,13 @@ use Magento\Framework\Exception\StateException; use Magento\Framework\Exception\InputException; use Magento\Framework\Data\Form\FormKey\Validator; +use Magento\Customer\Controller\AbstractAccount; /** * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class CreatePost extends \Magento\Customer\Controller\AbstractAccount +class CreatePost extends AbstractAccount implements CsrfAwareActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface @@ -273,6 +279,31 @@ protected function extractAddress() return $addressDataObject; } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $url = $this->urlModel->getUrl('*/*/create', ['_secure' => true]); + $resultRedirect->setUrl($this->_redirect->error($url)); + + return new InvalidRequestException( + $resultRedirect, + [new Phrase('Invalid Form Key. Please refresh the page.')] + ); + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return null; + } + /** * Create customer account action * @@ -282,17 +313,19 @@ protected function extractAddress() */ public function execute() { - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); if ($this->session->isLoggedIn() || !$this->registration->isAllowed()) { $resultRedirect->setPath('*/*/'); return $resultRedirect; } - if (!$this->getRequest()->isPost() || !$this->formKeyValidator->validate($this->getRequest())) { + if (!$this->getRequest()->isPost() + || !$this->formKeyValidator->validate($this->getRequest()) + ) { $url = $this->urlModel->getUrl('*/*/create', ['_secure' => true]); - $resultRedirect->setUrl($this->_redirect->error($url)); - return $resultRedirect; + return $this->resultRedirectFactory->create() + ->setUrl($this->_redirect->error($url)); } $this->session->regenerateId(); @@ -375,8 +408,7 @@ public function execute() $this->session->setCustomerFormData($this->getRequest()->getPostValue()); $defaultUrl = $this->urlModel->getUrl('*/*/create', ['_secure' => true]); - $resultRedirect->setUrl($this->_redirect->error($defaultUrl)); - return $resultRedirect; + return $resultRedirect->setUrl($this->_redirect->error($defaultUrl)); } /** diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index a10795533a2a5..aa5e088f9c892 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -10,8 +10,11 @@ use Magento\Customer\Model\AuthenticationInterface; use Magento\Customer\Model\Customer\Mapper; use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\CsrfAwareActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; @@ -21,12 +24,14 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\InvalidEmailOrPasswordException; use Magento\Framework\Exception\State\UserLockedException; +use Magento\Customer\Controller\AbstractAccount; +use Magento\Framework\Phrase; /** * Class EditPost * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class EditPost extends \Magento\Customer\Controller\AbstractAccount +class EditPost extends AbstractAccount implements CsrfAwareActionInterface { /** * Form code for data extractor @@ -131,6 +136,30 @@ private function getEmailNotification() } } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('*/*/edit'); + + return new InvalidRequestException( + $resultRedirect, + [new Phrase('Invalid Form Key. Please refresh the page.')] + ); + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return null; + } + /** * Change customer email or password action * @@ -190,7 +219,10 @@ public function execute() $this->session->setCustomerFormData($this->getRequest()->getPostValue()); } - return $resultRedirect->setPath('*/*/edit'); + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('*/*/edit'); + return $resultRedirect; } /** diff --git a/app/code/Magento/Customer/Controller/Account/Login.php b/app/code/Magento/Customer/Controller/Account/Login.php index 51c244ec0cfe9..d685191bf43b5 100644 --- a/app/code/Magento/Customer/Controller/Account/Login.php +++ b/app/code/Magento/Customer/Controller/Account/Login.php @@ -9,8 +9,9 @@ use Magento\Customer\Model\Session; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\PageFactory; +use Magento\Customer\Controller\AbstractAccount; -class Login extends \Magento\Customer\Controller\AbstractAccount +class Login extends AbstractAccount { /** * @var Session diff --git a/app/code/Magento/Customer/Controller/Account/LoginPost.php b/app/code/Magento/Customer/Controller/Account/LoginPost.php index 31e2a3aeca9e3..5c7eee78e5f4a 100644 --- a/app/code/Magento/Customer/Controller/Account/LoginPost.php +++ b/app/code/Magento/Customer/Controller/Account/LoginPost.php @@ -11,17 +11,23 @@ use Magento\Customer\Model\Session; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Model\Url as CustomerUrl; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Exception\EmailNotConfirmedException; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\State\UserLockedException; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Customer\Controller\AbstractAccount; +use Magento\Framework\Phrase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class LoginPost extends \Magento\Customer\Controller\AbstractAccount +class LoginPost extends AbstractAccount implements CsrfAwareActionInterface { /** * @var \Magento\Customer\Api\AccountManagementInterface @@ -131,6 +137,30 @@ private function getCookieMetadataFactory() return $this->cookieMetadataFactory; } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('*/*/'); + + return new InvalidRequestException( + $resultRedirect, + [new Phrase('Invalid Form Key. Please refresh the page.')] + ); + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return null; + } + /** * Login post action * @@ -172,31 +202,28 @@ public function execute() 'This account is not confirmed. <a href="%1">Click here</a> to resend confirmation email.', $value ); - $this->messageManager->addError($message); - $this->session->setUsername($login['username']); } catch (UserLockedException $e) { $message = __( 'The account sign-in was incorrect or your account is disabled temporarily. ' . 'Please wait and try again later.' ); - $this->messageManager->addError($message); - $this->session->setUsername($login['username']); } catch (AuthenticationException $e) { $message = __( 'The account sign-in was incorrect or your account is disabled temporarily. ' . 'Please wait and try again later.' ); - $this->messageManager->addError($message); - $this->session->setUsername($login['username']); } catch (LocalizedException $e) { $message = $e->getMessage(); - $this->messageManager->addError($message); - $this->session->setUsername($login['username']); } catch (\Exception $e) { // PA DSS violation: throwing or logging an exception here can disclose customer password $this->messageManager->addError( __('An unspecified error occurred. Please contact us for assistance.') ); + } finally { + if (isset($message)) { + $this->messageManager->addError($message); + $this->session->setUsername($login['username']); + } } } else { $this->messageManager->addError(__('A login and a password are required.')); diff --git a/app/code/Magento/Customer/Controller/Account/Logout.php b/app/code/Magento/Customer/Controller/Account/Logout.php index 3d5d5480c502b..19dabf9effa56 100644 --- a/app/code/Magento/Customer/Controller/Account/Logout.php +++ b/app/code/Magento/Customer/Controller/Account/Logout.php @@ -11,8 +11,9 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\Cookie\PhpCookieManager; +use Magento\Customer\Controller\AbstractAccount; -class Logout extends \Magento\Customer\Controller\AbstractAccount +class Logout extends AbstractAccount { /** * @var Session diff --git a/app/code/Magento/Customer/Controller/Address/FormPost.php b/app/code/Magento/Customer/Controller/Address/FormPost.php index 21334f51b1752..60e1f6eb172f5 100644 --- a/app/code/Magento/Customer/Controller/Address/FormPost.php +++ b/app/code/Magento/Customer/Controller/Address/FormPost.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Controller\Address; use Magento\Customer\Api\AddressRepositoryInterface; @@ -197,17 +198,17 @@ public function execute() try { $address = $this->_extractAddress(); $this->_addressRepository->save($address); - $this->messageManager->addSuccess(__('You saved the address.')); + $this->messageManager->addSuccessMessage(__('You saved the address.')); $url = $this->_buildUrl('*/*/index', ['_secure' => true]); return $this->resultRedirectFactory->create()->setUrl($this->_redirect->success($url)); } catch (InputException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); foreach ($e->getErrors() as $error) { - $this->messageManager->addError($error->getMessage()); + $this->messageManager->addErrorMessage($error->getMessage()); } } catch (\Exception $e) { $redirectUrl = $this->_buildUrl('*/*/index'); - $this->messageManager->addException($e, __('We can\'t save the address.')); + $this->messageManager->addExceptionMessage($e, __('We can\'t save the address.')); } $url = $redirectUrl; diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php index ebab2a42a02ec..e26b49aaebe7a 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php @@ -73,7 +73,7 @@ public function execute() /** * Return component referer url - * TODO: Technical dept referer url should be implement as a part of Action configuration in in appropriate way + * TODO: Technical dept referer url should be implement as a part of Action configuration in appropriate way * * @return null|string */ diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 63eb1efa64be6..fe17adcb09c0d 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -442,7 +442,7 @@ private function getAuthentication() } /** - * {@inheritdoc} + * @inheritdoc */ public function resendConfirmation($email, $websiteId = null, $redirectUrl = '') { @@ -465,7 +465,7 @@ public function resendConfirmation($email, $websiteId = null, $redirectUrl = '') } /** - * {@inheritdoc} + * @inheritdoc */ public function activate($email, $confirmationKey) { @@ -474,7 +474,7 @@ public function activate($email, $confirmationKey) } /** - * {@inheritdoc} + * @inheritdoc */ public function activateById($customerId, $confirmationKey) { @@ -514,7 +514,7 @@ private function activateCustomer($customer, $confirmationKey) } /** - * {@inheritdoc} + * @inheritdoc */ public function authenticate($username, $password) { @@ -549,7 +549,7 @@ public function authenticate($username, $password) } /** - * {@inheritdoc} + * @inheritdoc */ public function validateResetPasswordLinkToken($customerId, $resetPasswordLinkToken) { @@ -558,7 +558,7 @@ public function validateResetPasswordLinkToken($customerId, $resetPasswordLinkTo } /** - * {@inheritdoc} + * @inheritdoc */ public function initiatePasswordReset($email, $template, $websiteId = null) { @@ -611,7 +611,7 @@ private function handleUnknownTemplate($template) } /** - * {@inheritdoc} + * @inheritdoc */ public function resetPassword($email, $resetToken, $newPassword) { @@ -720,7 +720,7 @@ protected function getMinPasswordLength() } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfirmationStatus($customerId) { @@ -736,7 +736,7 @@ public function getConfirmationStatus($customerId) } /** - * {@inheritdoc} + * @inheritdoc */ public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') { @@ -758,7 +758,7 @@ public function createAccount(CustomerInterface $customer, $password = null, $re } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -794,6 +794,11 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash // Update 'created_in' value with actual store name if ($customer->getId() === null) { + $websiteId = $customer->getWebsiteId(); + if ($websiteId && !$this->isCustomerInStore($websiteId, $customer->getStoreId())) { + throw new LocalizedException(__('The store view is not in the associated website.')); + } + $storeName = $this->storeManager->getStore($customer->getStoreId())->getName(); $customer->setCreatedIn($storeName); } @@ -836,7 +841,7 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultBillingAddress($customerId) { @@ -845,7 +850,7 @@ public function getDefaultBillingAddress($customerId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultShippingAddress($customerId) { @@ -880,7 +885,7 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU } /** - * {@inheritdoc} + * @inheritdoc */ public function changePassword($email, $currentPassword, $newPassword) { @@ -893,7 +898,7 @@ public function changePassword($email, $currentPassword, $newPassword) } /** - * {@inheritdoc} + * @inheritdoc */ public function changePasswordById($customerId, $currentPassword, $newPassword) { @@ -961,7 +966,7 @@ private function getEavValidator() } /** - * {@inheritdoc} + * @inheritdoc */ public function validate(CustomerInterface $customer) { @@ -986,7 +991,7 @@ public function validate(CustomerInterface $customer) } /** - * {@inheritdoc} + * @inheritdoc */ public function isEmailAvailable($customerEmail, $websiteId = null) { @@ -1002,7 +1007,7 @@ public function isEmailAvailable($customerEmail, $websiteId = null) } /** - * {@inheritDoc} + * @inheritDoc */ public function isCustomerInStore($customerWebsiteId, $storeId) { @@ -1463,9 +1468,7 @@ private function destroyCustomerSessions($customerId) /** @var \Magento\Customer\Model\Visitor $visitor */ foreach ($visitorCollection->getItems() as $visitor) { $sessionId = $visitor->getSessionId(); - $this->sessionManager->start(); $this->saveHandler->destroy($sessionId); - $this->sessionManager->writeClose(); } } } diff --git a/app/code/Magento/Customer/Model/Address/Validator/Country.php b/app/code/Magento/Customer/Model/Address/Validator/Country.php index ff1020eba70ef..d7fb51dbbd442 100644 --- a/app/code/Magento/Customer/Model/Address/Validator/Country.php +++ b/app/code/Magento/Customer/Model/Address/Validator/Country.php @@ -88,6 +88,8 @@ private function validateCountry(AbstractAddress $address) * * @param AbstractAddress $address * @return array + * @throws \Zend_Validate_Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function validateRegion(AbstractAddress $address) { @@ -107,7 +109,7 @@ private function validateRegion(AbstractAddress $address) //If country actually has regions and requires you to //select one then it must be selected. $errors[] = __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'regionId']); - } elseif ($regionId && !in_array($regionId, $allowedRegions, true)) { + } elseif ($allowedRegions && $regionId && !in_array($regionId, $allowedRegions, true)) { //If a region is selected then checking if it exists. $errors[] = __( 'Invalid value of "%value" provided for the %fieldName field.', diff --git a/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php b/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php new file mode 100644 index 0000000000000..d4e0c5cce3401 --- /dev/null +++ b/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Indexer; + +use Magento\Customer\Model\ResourceModel\Group\CollectionFactory as CustomerGroupCollectionFactory; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Indexer\DimensionProviderInterface; + +class CustomerGroupDimensionProvider implements DimensionProviderInterface +{ + /** + * Name for customer group dimension for multidimensional indexer + * 'cg' - stands for 'customer_group' + */ + const DIMENSION_NAME = 'cg'; + + /** + * @var CustomerGroupCollectionFactory + */ + private $collectionFactory; + + /** + * @var \SplFixedArray + */ + private $customerGroupsDataIterator; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + public function __construct(CustomerGroupCollectionFactory $collectionFactory, DimensionFactory $dimensionFactory) + { + $this->dimensionFactory = $dimensionFactory; + $this->collectionFactory = $collectionFactory; + } + + public function getIterator(): \Traversable + { + foreach ($this->getCustomerGroups() as $customerGroup) { + yield $this->dimensionFactory->create(self::DIMENSION_NAME, (string)$customerGroup); + } + } + + /** + * @return array + */ + private function getCustomerGroups(): array + { + if ($this->customerGroupsDataIterator === null) { + $customerGroups = $this->collectionFactory->create()->getAllIds(); + $this->customerGroupsDataIterator = is_array($customerGroups) ? $customerGroups : []; + } + + return $this->customerGroupsDataIterator; + } +} diff --git a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataHydrator.php b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataHydrator.php index 190a3a38e0bf0..f61f064e3f97e 100644 --- a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataHydrator.php +++ b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataHydrator.php @@ -11,6 +11,7 @@ use Magento\Customer\Api\Data\OptionInterfaceFactory; use Magento\Customer\Api\Data\ValidationRuleInterface; use Magento\Customer\Api\Data\ValidationRuleInterfaceFactory; +use Magento\Customer\Model\Data\AttributeMetadata; use Magento\Framework\Reflection\DataObjectProcessor; /** @@ -120,7 +121,7 @@ public function extract($attributeMetadata) { return $this->dataObjectProcessor->buildOutputDataArray( $attributeMetadata, - AttributeMetadataInterface::class + AttributeMetadata::class ); } } diff --git a/app/code/Magento/Customer/Model/Metadata/Form/Text.php b/app/code/Magento/Customer/Model/Metadata/Form/Text.php index 9ef6df0a6d36e..c8b9a1e46a127 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/Text.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/Text.php @@ -5,8 +5,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Model\Metadata\Form; +use Magento\Customer\Api\Data\AttributeMetadataInterface; use Magento\Framework\Api\ArrayObjectSearch; class Text extends AbstractData @@ -19,7 +21,7 @@ class Text extends AbstractData /** * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Customer\Api\Data\AttributeMetadataInterface $attribute + * @param AttributeMetadataInterface $attribute * @param \Magento\Framework\Locale\ResolverInterface $localeResolver * @param string $value * @param string $entityTypeCode @@ -29,7 +31,7 @@ class Text extends AbstractData public function __construct( \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Psr\Log\LoggerInterface $logger, - \Magento\Customer\Api\Data\AttributeMetadataInterface $attribute, + AttributeMetadataInterface $attribute, \Magento\Framework\Locale\ResolverInterface $localeResolver, $value, $entityTypeCode, @@ -41,7 +43,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function extractValue(\Magento\Framework\App\RequestInterface $request) { @@ -49,7 +51,7 @@ public function extractValue(\Magento\Framework\App\RequestInterface $request) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -72,26 +74,7 @@ public function validateValue($value) return true; } - // validate length - $length = $this->_string->strlen(trim($value)); - - $validateRules = $attribute->getValidationRules(); - - $minTextLength = ArrayObjectSearch::getArrayElementByName( - $validateRules, - 'min_text_length' - ); - if ($minTextLength !== null && $length < $minTextLength) { - $errors[] = __('"%1" length must be equal or greater than %2 characters.', $label, $minTextLength); - } - - $maxTextLength = ArrayObjectSearch::getArrayElementByName( - $validateRules, - 'max_text_length' - ); - if ($maxTextLength !== null && $length > $maxTextLength) { - $errors[] = __('"%1" length must be equal or less than %2 characters.', $label, $maxTextLength); - } + $errors = $this->validateLength($value, $attribute, $errors); $result = $this->_validateInputRule($value); if ($result !== true) { @@ -105,7 +88,7 @@ public function validateValue($value) } /** - * {@inheritdoc} + * @inheritdoc */ public function compactValue($value) { @@ -113,7 +96,7 @@ public function compactValue($value) } /** - * {@inheritdoc} + * @inheritdoc */ public function restoreValue($value) { @@ -121,10 +104,48 @@ public function restoreValue($value) } /** - * {@inheritdoc} + * @inheritdoc */ public function outputValue($format = \Magento\Customer\Model\Metadata\ElementFactory::OUTPUT_FORMAT_TEXT) { return $this->_applyOutputFilter($this->_value); } + + /** + * Length validation + * + * @param mixed $value + * @param AttributeMetadataInterface $attribute + * @param array $errors + * @return array + */ + private function validateLength($value, AttributeMetadataInterface $attribute, array $errors): array + { + // validate length + $label = __($attribute->getStoreLabel()); + + $length = $this->_string->strlen(trim($value)); + + $validateRules = $attribute->getValidationRules(); + + if (!empty(ArrayObjectSearch::getArrayElementByName($validateRules, 'input_validation'))) { + $minTextLength = ArrayObjectSearch::getArrayElementByName( + $validateRules, + 'min_text_length' + ); + if ($minTextLength !== null && $length < $minTextLength) { + $errors[] = __('"%1" length must be equal or greater than %2 characters.', $label, $minTextLength); + } + + $maxTextLength = ArrayObjectSearch::getArrayElementByName( + $validateRules, + 'max_text_length' + ); + if ($maxTextLength !== null && $length > $maxTextLength) { + $errors[] = __('"%1" length must be equal or less than %2 characters.', $label, $maxTextLength); + } + } + + return $errors; + } } diff --git a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php index cb73b7ee1cb36..31e0e2727436b 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/GroupRepository.php @@ -156,6 +156,11 @@ public function save(\Magento\Customer\Api\Data\GroupInterface $group) ->setCode($groupModel->getCode()) ->setTaxClassId($groupModel->getTaxClassId()) ->setTaxClassName($groupModel->getTaxClassName()); + + if ($group->getExtensionAttributes()) { + $groupDataObject->setExtensionAttributes($group->getExtensionAttributes()); + } + return $groupDataObject; } diff --git a/app/code/Magento/Customer/Model/Session.php b/app/code/Magento/Customer/Model/Session.php index 680e68b5c4c0f..5900fed218edf 100644 --- a/app/code/Magento/Customer/Model/Session.php +++ b/app/code/Magento/Customer/Model/Session.php @@ -487,7 +487,7 @@ public function authenticate($loginUrl = null) $this->response->setRedirect($loginUrl); } else { $arguments = $this->_customerUrl->getLoginUrlParams(); - if ($this->_session->getCookieShouldBeReceived() && $this->_createUrl()->getUseSession()) { + if ($this->_createUrl()->getUseSession()) { $arguments += [ '_query' => [ $this->sidResolver->getSessionIdQueryParam($this->_session) => $this->_session->getSessionId(), diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php index 4624dd8b6bcf5..763a7afbfa851 100644 --- a/app/code/Magento/Customer/Model/Visitor.php +++ b/app/code/Magento/Customer/Model/Visitor.php @@ -6,8 +6,6 @@ namespace Magento\Customer\Model; -use Magento\Framework\Indexer\StateInterface; - /** * Class Visitor * @package Magento\Customer\Model diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml index 1a4e9071d306b..7be36ffbd9bc4 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="LoginToStorefrontActionGroup"> <arguments> <argument name="Customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml index 3d6e0fb54b054..639c64b773a2d 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="OpenEditCustomerFromAdminActionGroup"> <arguments> <argument name="customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index de8418774f794..79cc00d4474b1 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SignUpNewUserFromStorefrontActionGroup"> <arguments> <argument name="Customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index 19194ae2e5423..6ed75861fd420 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomerAddressSimple" type="address"> <data key="id">0</data> <data key="customer_id">12</data> @@ -83,4 +83,27 @@ <data key="default_shipping">Yes</data> <requiredEntity type="region">RegionCA</requiredEntity> </entity> + <!--If required other field can be added to UK_Address entity, dont modify any existing data--> + <entity name="UK_Address" type="address"> + <array key="street"> + <item>7700 xyz street</item> + <item>113</item> + </array> + <data key="city">London</data> + <data key="country_id">GB</data> + <data key="telephone">512-345-6789</data> + <data key="province">JS</data> + </entity> + <entity name="UK_Not_Default_Address" type="address"> + <data key="firstname">Jane</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>172, Westminster Bridge Rd</item> + </array> + <data key="city">London</data> + <data key="postcode">SE1 7RW</data> + <data key="country_id">GB</data> + <data key="telephone">444-44-444-44</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index 1827824ba4b92..baf6772843894 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomerEntityOne" type="customer"> <data key="group_id">0</data> <data key="default_billing">defaultBillingValue</data> @@ -45,6 +45,20 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="Simple_US_Customer_Multiple_Addresses" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_NY</requiredEntity> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + </entity> <entity name="Simple_US_Customer_NY" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml index cc8e16f017f8e..c1f11c9e9c390 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="GeneralCustomerGroup" type="customerGroup"> <data key="code">General</data> <data key="tax_class_id">3</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/ExtensionAttributeSimple.xml b/app/code/Magento/Customer/Test/Mftf/Data/ExtensionAttributeSimple.xml index fee4463709dd5..90540251877d5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/ExtensionAttributeSimple.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/ExtensionAttributeSimple.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ExtensionAttributeSimple" type="extension_attribute"> <data key="is_subscribed">true</data> </entity> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml index 747f2d59745a1..523a463e5dea2 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomerRegionOne" type="region"> <data key="region_code">100</data> <data key="region_id">12</data> @@ -20,11 +20,11 @@ <entity name="RegionCA" type="region"> <data key="region">California</data> <data key="region_code">CA</data> - <data key="region_id">2</data> + <data key="region_id">12</data> </entity> <entity name="RegionNY" type="region"> <data key="region">New York</data> <data key="region_code">NY</data> - <data key="region_id">3</data> + <data key="region_id">43</data> </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/address-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/address-meta.xml index deb911f244f11..10f63a5a2a820 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/address-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/address-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateAddress" dataType="address" type="create"> <field key="region">region</field> <field key="country_id">string</field> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-meta.xml index ab2ee2aeddb54..0d8aeb6614bf4 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateCustomer" dataType="customer" type="create" auth="adminOauth" url="/V1/customers" method="POST"> <contentType>application/json</contentType> <object dataType="customer" key="customer"> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_extension_attribute-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_extension_attribute-meta.xml index 8561e937221a9..06c7b74aef002 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_extension_attribute-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_extension_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateCustomerExtensionAttribute" dataType="customer_extension_attribute" type="create"> <field key="is_subscribed">boolean</field> <field key="extension_attribute">customer_nested_extension_attribute</field> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_nested_extension_attribute-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_nested_extension_attribute-meta.xml index eb9829cca4981..a2741b7817b16 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_nested_extension_attribute-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_nested_extension_attribute-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateNestedExtensionAttribute" dataType="customer_nested_extension_attribute" type="create"> <field key="id">integer</field> <field key="customer_id">integer</field> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/region-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/region-meta.xml index 3dd019462c846..5c21c5318e58b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/region-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/region-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateRegion" dataType="region" type="create"> <field key="region_code">string</field> <field key="region">string</field> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerPage.xml index 06ab646aa4c75..114c737e361ed 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCustomerPage" url="/customer/index/" area="admin" module="Magento_Customer"> <section name="AdminCustomerGridMainActionsSection"/> <section name="AdminCustomerMessagesSection"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml index 9a28bad4e0d6a..31dad24ba8372 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminEditCustomerPage" url="/customer/index/edit/id/{{var1}}" area="admin" module="Magento_Customer" parameterized="true"> <section name="AdminCustomerAccountInformationSection"/> <section name="AdminCustomerMainActionsSection"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminNewCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminNewCustomerPage.xml index 646f03181d8fa..57a30d6f98921 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminNewCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminNewCustomerPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminNewCustomerPage" url="/customer/index/new" area="admin" module="Magento_Customer"> <section name="AdminCustomerAccountInformationSection"/> <section name="AdminCustomerMainActionsSection"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml index ba61cbb0bca42..e2ebf638934c6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerCreatePage" url="/customer/account/create/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerCreateFormSection" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml index 941e247e18b8c..c4f03659c12af 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerDashboardPage" url="/customer/account/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerDashboardAccountInformationSection" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderPage.xml index bd25c67c8c907..05c4c71a61e94 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerOrderPage" url="sales/order/view/order_id/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerOrderViewSection"/> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderViewPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderViewPage.xml index 7e6cebe6f3c78..2305bd3a9b82f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderViewPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerOrderViewPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerOrderViewPage" url="sales/order/view/order_id/{{var1}}" area="storefront" module="Magento_Customer" parameterized="true"> <section name="StorefrontCustomerOrderSection" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml index f6673227beada..0d4fef8f6e967 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerSignInPage" url="/customer/account/login/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerSignInFormSection" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontHomePage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontHomePage.xml index 6b65bd97e8cb3..a466ceab2f7ed 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontHomePage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontHomePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontHomePage" url="/" area="storefront" module="Magento_Customer"> <section name="StorefrontPanelHeader" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index 647cc6e3ee11f..a485069341a03 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerAccountInformationSection"> <element name="accountInformationTitle" type="text" selector=".admin__page-nav-title"/> <element name="firstName" type="input" selector="input[name='customer[firstname]']"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml index 7d106a35f0e13..bc69621e9f22f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerFiltersSection"> <element name="filtersButton" type="button" selector="#container > div > div.admin__data-grid-header > div:nth-child(1) > div.data-grid-filters-actions-wrap > div > button" timeout="30"/> <element name="nameInput" type="input" selector="input[name=name]"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml index 760b2c3663322..e3616b0d59d90 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerGridMainActionsSection"> <element name="addNewCustomer" type="button" selector="#add" timeout="30"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml index 515d5eed1124b..d9d3bfe7f737c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerGridSection"> <element name="customerGrid" type="text" selector="table[data-role='grid']"/> <element name="firstRowEditLink" type="text" selector="tr[data-repeat-index='0'] .action-menu-item" timeout="30"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml index 1aadcb2fa469f..37ea99d652fb9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerMainActionsSection"> <element name="saveButton" type="button" selector="#save" timeout="30"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMessagesSection.xml index 08c29473a7ee6..b11142fd1ce2e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMessagesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml index 76feb2624b5ed..a28c6d5ff5e2d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditCustomerInformationSection"> <element name="orders" type="button" selector="#tab_orders_content" timeout="30"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml index bce4a7e848c13..89fed43184b84 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerOrdersSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditCustomerOrdersSection"> <element name="orderGrid" type="text" selector="#customer_orders_grid_table"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml index adf898a65f212..2b5662cdd623e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerCreateFormSection"> <element name="firstnameField" type="input" selector="#firstname"/> <element name="lastnameField" type="input" selector="#lastname"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml index 21205c6d5d91e..70d1bb6675db5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerDashboardAccountInformationSection"> <element name="ContactInformation" type="textarea" selector=".box.box-information .box-content"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index c39dfef5f74e7..253a6a83c9438 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml @@ -7,9 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerOrderSection"> <element name="productCustomOptions" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[normalize-space(.)='{{var3}}']" parameterized="true"/> <element name="productCustomOptionsFile" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[contains(.,'{{var3}}')]" parameterized="true"/> + <element name="productCustomOptionsLink" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd//a[text() = '{{var3}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml index 9ea271dad7b21..53b07b2b6a51c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerOrderViewSection"> <element name="reorder" type="text" selector="a.action.order" timeout="30"/> <element name="orderTitle" type="text" selector=".page-title span"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index 9cc4a43d31bc6..8480dc6e9d2a6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerSignInFormSection"> <element name="emailField" type="input" selector="#email"/> <element name="passwordField" type="input" selector="#pass"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index 65e7aa7a12113..06b82db767ab5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontPanelHeaderSection"> <element name="WelcomeMessage" type="text" selector=".greet.welcome span"/> <element name="createAnAccountLink" type="select" selector=".panel.header li:nth-child(3)" timeout="30"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml new file mode 100644 index 0000000000000..01f35439f23b8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AddingProductWithExpiredSessionTest"> + <annotations> + <title value="Adding a product to cart from category page with an expired session"/> + <description value="Adding a product to cart from category page with an expired session"/> + <features value="Module/ Catalog"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93289"/> + <stories value="MAGETWO-66666: Adding a product to cart from category page with an expired session does not allow product to be added"/> + <group value="customer"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <!--Navigate to a category page --> + <amOnPage url="$$createSimpleProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- Remove PHPSESSID and form_key to replicate an expired session--> + <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> + <resetCookie userInput="form_key" stepKey="resetCookieForCart2"/> + + <!-- "Add to Cart" any product--> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> + <see stepKey="assertErrorMessage" userInput="Your session has expired"/> + <after> + <!--Delete created product--> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index bde15b31ff1e6..9dd2127fa28b2 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCustomerTest"> <annotations> <features value="Customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index a9563c4cc93d8..901018c2fd074 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <annotations> <features value="End to End scenarios"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml index 3670cdba3872d..97c932f0cb28a 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCreateCustomerTest"> <annotations> <features value="Customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml index ec669be165e68..250da68786688 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontPersistedCustomerLoginTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontPersistedCustomerLoginTest"> <annotations> <features value="Customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/composer.json b/app/code/Magento/Customer/Test/Mftf/composer.json deleted file mode 100644 index 4bf83a4bf800a..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/composer.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "magento/functional-test-module-customer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-newsletter": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-review": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-customer-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/EditPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/EditPostTest.php deleted file mode 100644 index aaa1c17027928..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/EditPostTest.php +++ /dev/null @@ -1,795 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Controller\Account; - -use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Controller\Account\EditPost; -use Magento\Customer\Model\AuthenticationInterface; -use Magento\Customer\Model\CustomerExtractor; -use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Customer\Model\Session; -use Magento\Framework\App\Action\Context; -use Magento\Framework\App\Request\Http; -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\Controller\Result\RedirectFactory; -use Magento\Framework\Data\Form\FormKey\Validator; -use Magento\Framework\Message\ManagerInterface; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class EditPostTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var EditPost - */ - protected $model; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $context; - - /** - * @var Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerSession; - - /** - * @var \Magento\Customer\Model\AccountManagement|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerAccountManagement; - - /** - * @var CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerRepository; - - /** - * @var Validator|\PHPUnit_Framework_MockObject_MockObject - */ - protected $validator; - - /** - * @var CustomerExtractor|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerExtractor; - - /** - * @var EmailNotificationInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $emailNotification; - - /** - * @var RedirectFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectFactory; - - /** - * @var Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirect; - - /** - * @var Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $request; - - /** - * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManager; - - /** - * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $eventManager; - - /** - * @var AuthenticationInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $authenticationMock; - - /** - * @var \Magento\Customer\Model\Customer\Mapper|\PHPUnit_Framework_MockObject_MockObject - */ - private $customerMapperMock; - - protected function setUp() - { - $this->prepareContext(); - - $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->setMethods(['getCustomerId', 'setCustomerFormData', 'logout', 'start']) - ->getMock(); - - $this->customerAccountManagement = $this->getMockBuilder(\Magento\Customer\Model\AccountManagement::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerRepository = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - - $this->validator = $this->getMockBuilder(\Magento\Framework\Data\Form\FormKey\Validator::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerExtractor = $this->getMockBuilder(\Magento\Customer\Model\CustomerExtractor::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->emailNotification = $this->getMockBuilder(EmailNotificationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerMapperMock = $this->getMockBuilder(\Magento\Customer\Model\Customer\Mapper::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->model = new EditPost( - $this->context, - $this->customerSession, - $this->customerAccountManagement, - $this->customerRepository, - $this->validator, - $this->customerExtractor - ); - - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $objectManager->setBackwardCompatibleProperty( - $this->model, - 'emailNotification', - $this->emailNotification - ); - - $objectManager->setBackwardCompatibleProperty( - $this->model, - 'authentication', - $this->authenticationMock - ); - $objectManager->setBackwardCompatibleProperty( - $this->model, - 'customerMapper', - $this->customerMapperMock - ); - } - - public function testInvalidFormKey() - { - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(false); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/edit') - ->willReturnSelf(); - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - public function testNoPostValues() - { - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('isPost') - ->willReturn(false); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/edit') - ->willReturnSelf(); - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - public function testGeneralSave() - { - $customerId = 1; - $currentPassword = '1234567'; - $customerEmail = 'customer@example.com'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->getMockForAbstractClass(); - $currentCustomerMock = $this->getCurrentCustomerMock($customerId, $address); - $newCustomerMock = $this->getNewCustomerMock($customerId, $address); - - $currentCustomerMock->expects($this->any()) - ->method('getEmail') - ->willReturn($customerEmail); - - $this->customerMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($currentCustomerMock) - ->willReturn([]); - - $this->customerSession->expects($this->once()) - ->method('getCustomerId') - ->willReturn($customerId); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($currentCustomerMock); - - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('isPost') - ->willReturn(true); - - $this->request->expects($this->exactly(3)) - ->method('getParam') - ->withConsecutive( - ['change_email'], - ['change_email'], - ['change_password'] - ) - ->willReturnOnConsecutiveCalls(true, true, false); - - $this->request->expects($this->once()) - ->method('getPost') - ->with('current_password') - ->willReturn($currentPassword); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($currentCustomerMock); - - $this->customerRepository->expects($this->once()) - ->method('save') - ->with($newCustomerMock) - ->willReturnSelf(); - - $this->customerExtractor->expects($this->once()) - ->method('extract') - ->with('customer_account_edit', $this->request) - ->willReturn($newCustomerMock); - - $this->emailNotification->expects($this->once()) - ->method('credentialsChanged') - ->with($currentCustomerMock, $customerEmail, false) - ->willReturnSelf(); - - $newCustomerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - - $this->eventManager->expects($this->once()) - ->method('dispatch') - ->with( - 'customer_account_edited', - ['email' => $customerEmail] - ); - - $this->messageManager->expects($this->once()) - ->method('addSuccess') - ->with(__('You saved the account information.')) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('customer/account') - ->willReturnSelf(); - - $this->authenticationMock->expects($this->once()) - ->method('authenticate') - ->willReturn(true); - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - /** - * @param int $testNumber - * @param string $exceptionClass - * @param string $errorMessage - * - * @dataProvider changeEmailExceptionDataProvider - */ - public function testChangeEmailException($testNumber, $exceptionClass, $errorMessage) - { - $customerId = 1; - $password = '1234567'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->getMockForAbstractClass(); - - $currentCustomerMock = $this->getCurrentCustomerMock($customerId, $address); - $newCustomerMock = $this->getNewCustomerMock($customerId, $address); - - $this->customerMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($currentCustomerMock) - ->willReturn([]); - - $this->customerExtractor->expects($this->once()) - ->method('extract') - ->with('customer_account_edit', $this->request) - ->willReturn($newCustomerMock); - - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('isPost') - ->willReturn(true); - - $this->customerSession->expects($this->once()) - ->method('getCustomerId') - ->willReturn($customerId); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($currentCustomerMock); - - $this->request->expects($this->any()) - ->method('getParam') - ->with('change_email') - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('getPost') - ->with('current_password') - ->willReturn($password); - - $exception = new $exceptionClass($errorMessage); - $this->authenticationMock->expects($this->once()) - ->method('authenticate') - ->willThrowException($exception); - - $this->messageManager->expects($this->once()) - ->method('addError') - ->with($errorMessage) - ->willReturnSelf(); - - if ($testNumber==1) { - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/edit') - ->willReturnSelf(); - } - - if ($testNumber==2) { - $this->customerSession->expects($this->once()) - ->method('logout'); - - $this->customerSession->expects($this->once()) - ->method('start'); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('customer/account/login') - ->willReturnSelf(); - } - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - /** - * @return array - */ - public function changeEmailExceptionDataProvider() - { - return [ - [ - 'testNumber' => 1, - 'exceptionClass' => \Magento\Framework\Exception\InvalidEmailOrPasswordException::class, - 'errorMessage' => __("The password doesn't match this account. Verify the password and try again.") - ], - [ - 'testNumber' => 2, - 'exceptionClass' => \Magento\Framework\Exception\State\UserLockedException::class, - 'errorMessage' => __('The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.') - ] - ]; - } - - /** - * @param string $currentPassword - * @param string $newPassword - * @param string $confirmationPassword - * @param [] $errors - * - * @dataProvider changePasswordDataProvider - */ - public function testChangePassword( - $currentPassword, - $newPassword, - $confirmationPassword, - $errors - ) { - $customerId = 1; - $customerEmail = 'user1@example.com'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->getMockForAbstractClass(); - - $currentCustomerMock = $this->getCurrentCustomerMock($customerId, $address); - $newCustomerMock = $this->getNewCustomerMock($customerId, $address); - - $this->customerMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($currentCustomerMock) - ->willReturn([]); - - $this->customerSession->expects($this->once()) - ->method('getCustomerId') - ->willReturn($customerId); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($currentCustomerMock); - - $this->customerExtractor->expects($this->once()) - ->method('extract') - ->with('customer_account_edit', $this->request) - ->willReturn($newCustomerMock); - - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('isPost') - ->willReturn(true); - - $this->request->expects($this->exactly(3)) - ->method('getParam') - ->withConsecutive( - ['change_email'], - ['change_email'], - ['change_password'] - ) - ->willReturnOnConsecutiveCalls(false, false, true); - - $this->request->expects($this->any()) - ->method('getPostValue') - ->willReturn(true); - - $this->request->expects($this->exactly(3)) - ->method('getPost') - ->willReturnMap([ - ['current_password', null, $currentPassword], - ['password', null, $newPassword], - ['password_confirmation', null, $confirmationPassword], - ]); - - $currentCustomerMock->expects($this->any()) - ->method('getEmail') - ->willReturn($customerEmail); - - // Prepare errors processing - if ($errors['counter'] > 0) { - $this->mockChangePasswordErrors($currentPassword, $newPassword, $errors, $customerEmail); - } else { - $this->customerAccountManagement->expects($this->once()) - ->method('changePassword') - ->with($customerEmail, $currentPassword, $newPassword) - ->willReturnSelf(); - - $this->customerRepository->expects($this->once()) - ->method('save') - ->with($newCustomerMock) - ->willReturnSelf(); - - $this->messageManager->expects($this->once()) - ->method('addSuccess') - ->with(__('You saved the account information.')) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('customer/account') - ->willReturnSelf(); - } - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - /** - * @return array - */ - public function changePasswordDataProvider() - { - return [ - [ - 'current_password' => '', - 'new_password' => '', - 'confirmation_password' => '', - 'errors' => [ - 'counter' => 1, - 'message' => __('Please enter new password.'), - ] - ], - [ - 'current_password' => '', - 'new_password' => 'user2@example.com', - 'confirmation_password' => 'user3@example.com', - 'errors' => [ - 'counter' => 1, - 'message' => __('Password confirmation doesn\'t match entered password.'), - ] - ], - [ - 'current_password' => 'user1@example.com', - 'new_password' => 'user2@example.com', - 'confirmation_password' => 'user2@example.com', - 'errors' => [ - 'counter' => 0, - 'message' => '', - ] - ], - [ - 'current_password' => 'user1@example.com', - 'new_password' => 'user2@example.com', - 'confirmation_password' => 'user2@example.com', - 'errors' => [ - 'counter' => 1, - 'message' => 'AuthenticationException', - 'exception' => \Magento\Framework\Exception\AuthenticationException::class, - ] - ], - [ - 'current_password' => 'user1@example.com', - 'new_password' => 'user2@example.com', - 'confirmation_password' => 'user2@example.com', - 'errors' => [ - 'counter' => 1, - 'message' => 'Exception', - 'exception' => '\Exception', - ] - ] - ]; - } - - /** - * @param string $message - * @param string $exception - * - * @dataProvider exceptionDataProvider - */ - public function testGeneralException( - $message, - $exception - ) { - $customerId = 1; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->getMockForAbstractClass(); - - $currentCustomerMock = $this->getCurrentCustomerMock($customerId, $address); - $newCustomerMock = $this->getNewCustomerMock($customerId, $address); - - $this->customerMapperMock->expects($this->once()) - ->method('toFlatArray') - ->with($currentCustomerMock) - ->willReturn([]); - - $exception = new $exception(__($message)); - - $this->validator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->request->expects($this->once()) - ->method('isPost') - ->willReturn(true); - - $this->request->expects($this->exactly(3)) - ->method('getParam') - ->withConsecutive( - ['change_email'], - ['change_email'], - ['change_password'] - ) - ->willReturn(false); - - $this->request->expects($this->any()) - ->method('getPostValue') - ->willReturn(true); - - $this->customerSession->expects($this->once()) - ->method('getCustomerId') - ->willReturn($customerId); - $this->customerSession->expects($this->once()) - ->method('setCustomerFormData') - ->with(true) - ->willReturnSelf(); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($currentCustomerMock); - $this->customerRepository->expects($this->once()) - ->method('save') - ->with($newCustomerMock) - ->willThrowException($exception); - - $this->customerExtractor->expects($this->once()) - ->method('extract') - ->with('customer_account_edit', $this->request) - ->willReturn($newCustomerMock); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/edit') - ->willReturnSelf(); - - $this->assertSame($this->resultRedirect, $this->model->execute()); - } - - /** - * @return array - */ - public function exceptionDataProvider() - { - return [ - [ - 'message' => 'LocalizedException', - 'exception' => \Magento\Framework\Exception\LocalizedException::class, - ], - [ - 'message' => 'Exception', - 'exception' => '\Exception', - ], - ]; - } - - protected function prepareContext() - { - $this->context = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultRedirectFactory = $this->getMockBuilder( - \Magento\Framework\Controller\Result\RedirectFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - - $this->resultRedirect = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->context->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($this->resultRedirectFactory); - - $this->context->expects($this->any()) - ->method('getRequest') - ->willReturn($this->request); - - $this->context->expects($this->any()) - ->method('getMessageManager') - ->willReturn($this->messageManager); - - $this->eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->context->expects($this->any()) - ->method('getEventManager') - ->willReturn($this->eventManager); - - $this->resultRedirectFactory->expects($this->any()) - ->method('create') - ->willReturn($this->resultRedirect); - } - - /** - * @param int $customerId - * @param \PHPUnit_Framework_MockObject_MockObject $address - * @return \PHPUnit_Framework_MockObject_MockObject - */ - protected function getNewCustomerMock($customerId, $address) - { - $newCustomerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $newCustomerMock->expects($this->once()) - ->method('setId') - ->with($customerId) - ->willReturnSelf(); - $newCustomerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn(null); - $newCustomerMock->expects($this->once()) - ->method('setAddresses') - ->with([$address]) - ->willReturn(null); - - return $newCustomerMock; - } - - /** - * @param int $customerId - * @param \PHPUnit_Framework_MockObject_MockObject $address - * @return \PHPUnit_Framework_MockObject_MockObject - */ - protected function getCurrentCustomerMock($customerId, $address) - { - $currentCustomerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $currentCustomerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - - $currentCustomerMock->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - - return $currentCustomerMock; - } - - /** - * @param string $currentPassword - * @param string $newPassword - * @param [] $errors - * @param string $customerEmail - * @return void - */ - protected function mockChangePasswordErrors($currentPassword, $newPassword, $errors, $customerEmail) - { - if (!empty($errors['exception'])) { - $exception = new $errors['exception'](__($errors['message'])); - - $this->customerAccountManagement->expects($this->once()) - ->method('changePassword') - ->with($customerEmail, $currentPassword, $newPassword) - ->willThrowException($exception); - - $this->messageManager->expects($this->any()) - ->method('addException') - ->with($exception, __('We can\'t save the customer.')) - ->willReturnSelf(); - } - - $this->customerSession->expects($this->once()) - ->method('setCustomerFormData') - ->with(true) - ->willReturnSelf(); - - $this->messageManager->expects($this->any()) - ->method('addError') - ->with($errors['message']) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->any()) - ->method('setPath') - ->with('*/*/edit') - ->willReturnSelf(); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php index 4ad1b5cbc96bd..c2a795fc95016 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Controller\Address; use Magento\Customer\Api\AddressRepositoryInterface; @@ -162,6 +163,9 @@ class FormPostTest extends \PHPUnit\Framework\TestCase */ private $customerAddressMapper; + /** + * {@inheritDoc} + */ protected function setUp() { $this->prepareContext(); @@ -230,7 +234,10 @@ protected function setUp() ); } - protected function prepareContext() + /** + * Prepares context + */ + protected function prepareContext(): void { $this->context = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) ->disableOriginalConstructor() @@ -284,7 +291,10 @@ protected function prepareContext() ->willReturn($this->messageManager); } - protected function prepareAddress() + /** + * Prepare address + */ + protected function prepareAddress(): void { $this->addressRepository = $this->getMockBuilder(\Magento\Customer\Api\AddressRepositoryInterface::class) ->getMockForAbstractClass(); @@ -303,7 +313,10 @@ protected function prepareAddress() ->willReturn($this->addressData); } - protected function prepareRegion() + /** + * Prepare region + */ + protected function prepareRegion(): void { $this->region = $this->getMockBuilder(\Magento\Directory\Model\Region::class) ->disableOriginalConstructor() @@ -335,7 +348,10 @@ protected function prepareRegion() ->willReturn($this->regionData); } - protected function prepareForm() + /** + * Prepare form + */ + protected function prepareForm(): void { $this->form = $this->getMockBuilder(\Magento\Customer\Model\Metadata\Form::class) ->disableOriginalConstructor() @@ -346,7 +362,10 @@ protected function prepareForm() ->getMock(); } - public function testExecuteNoFormKey() + /** + * Test form without formKey + */ + public function testExecuteNoFormKey(): void { $this->formKeyValidator->expects($this->once()) ->method('validate') @@ -361,7 +380,10 @@ public function testExecuteNoFormKey() $this->assertEquals($this->resultRedirect, $this->model->execute()); } - public function testExecuteNoPostData() + /** + * Test executing without post data + */ + public function testExecuteNoPostData(): void { $postValue = 'post_value'; $url = 'url'; @@ -409,10 +431,11 @@ public function testExecuteNoPostData() } /** + * Tests executing + * * @param int $addressId * @param int $countryId * @param int $customerId - * @param bool $isRegionRequired * @param int $regionId * @param string $region * @param string $regionCode @@ -433,7 +456,7 @@ public function testExecute( $newRegionId, $newRegion, $newRegionCode - ) { + ): void { $existingAddressData = [ 'country_id' => $countryId, 'region_id' => $regionId, @@ -517,7 +540,8 @@ public function testExecute( ->willReturnMap([ [ $this->regionData, - $regionData, \Magento\Customer\Api\Data\RegionInterface::class, + $regionData, + \Magento\Customer\Api\Data\RegionInterface::class, $this->dataObjectHelper, ], [ @@ -549,7 +573,7 @@ public function testExecute( ->willReturnSelf(); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('You saved the address.')) ->willReturnSelf(); @@ -581,7 +605,7 @@ public function testExecute( /** * @return array */ - public function dataProviderTestExecute() + public function dataProviderTestExecute(): array { return [ [1, 1, 1, null, '', null, '', null, ''], @@ -612,7 +636,10 @@ public function dataProviderTestExecute() ]; } - public function testExecuteInputException() + /** + * Tests input exception + */ + public function testExecuteInputException(): void { $addressId = 1; $postValue = 'post_value'; @@ -640,7 +667,7 @@ public function testExecuteInputException() ->willThrowException(new InputException(__('InputException'))); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('InputException') ->willReturnSelf(); @@ -674,7 +701,10 @@ public function testExecuteInputException() $this->assertEquals($this->resultRedirect, $this->model->execute()); } - public function testExecuteException() + /** + * Tests exception + */ + public function testExecuteException(): void { $addressId = 1; $postValue = 'post_value'; @@ -703,7 +733,7 @@ public function testExecuteException() ->willThrowException($exception); $this->messageManager->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, __('We can\'t save the address.')) ->willReturnSelf(); diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 9e3a16a307923..aad20f757e91e 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -318,7 +318,7 @@ public function testCreateAccountWithPasswordHashWithCustomerWithoutStoreId() ->method('getId') ->willReturn($defaultStoreId); $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) + $website->expects($this->atLeastOnce()) ->method('getStoreIds') ->willReturn([1, 2, 3]); $website->expects($this->once()) @@ -354,7 +354,7 @@ public function testCreateAccountWithPasswordHashWithCustomerWithoutStoreId() ->with($customerEmail) ->willReturn($customer); $this->share - ->expects($this->once()) + ->expects($this->atLeastOnce()) ->method('isWebsiteScope') ->willReturn(true); $this->storeManager @@ -545,6 +545,7 @@ public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedExce { $storeId = 1; $storeName = 'store_name'; + $websiteId = 1; $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) @@ -556,6 +557,9 @@ public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedExce $customerMock->expects($this->atLeastOnce()) ->method('getStoreId') ->willReturn($storeId); + $customerMock->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); $customerMock->expects($this->once()) ->method('setCreatedIn') ->with($storeName) @@ -567,6 +571,19 @@ public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedExce ->method('setAddresses') ->with(null) ->willReturnSelf(); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) ->disableOriginalConstructor() @@ -576,7 +593,7 @@ public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedExce ->method('getName') ->willReturn($storeName); - $this->storeManager->expects($this->exactly(2)) + $this->storeManager->expects($this->exactly(1)) ->method('getStore') ->with($storeId) ->willReturn($storeMock); @@ -772,6 +789,9 @@ public function testCreateAccountWithPasswordInputException( $this->accountManagement->createAccount($customer, $password); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testCreateAccountInputExceptionExtraLongPassword() { $password = '257*chars*************************************************************************************' @@ -1129,11 +1149,11 @@ protected function prepareInitiatePasswordReset($email, $templateIdentifier, $se } /** - * @param $email - * @param $templateIdentifier - * @param $sender - * @param $storeId - * @param $customerName + * @param string $email + * @param int $templateIdentifier + * @param string $sender + * @param int $storeId + * @param string $customerName */ protected function prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName) { @@ -1168,6 +1188,9 @@ protected function prepareEmailSend($email, $templateIdentifier, $sender, $store ->method('sendMessage'); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testInitiatePasswordResetEmailReminder() { $customerId = 1; @@ -1191,6 +1214,9 @@ public function testInitiatePasswordResetEmailReminder() $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testInitiatePasswordResetEmailReset() { $storeId = 1; @@ -1213,6 +1239,9 @@ public function testInitiatePasswordResetEmailReset() $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testInitiatePasswordResetNoTemplate() { $storeId = 1; @@ -1332,7 +1361,6 @@ private function reInitModel() $this->prepareDateTimeFactory(); $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) ->disableOriginalConstructor() - ->setMethods(['destroy', 'start', 'writeClose']) ->getMockForAbstractClass(); $this->visitorCollectionFactory = $this->getMockBuilder( \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class @@ -1465,8 +1493,6 @@ public function testChangePassword() ->method('save') ->with($customer); - $this->sessionManager->expects($this->atLeastOnce())->method('start'); - $this->sessionManager->expects($this->atLeastOnce())->method('writeClose'); $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) @@ -1492,6 +1518,9 @@ public function testChangePassword() $this->assertTrue($this->accountManagement->changePassword($email, $currentPassword, $newPassword)); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testResetPassword() { $customerEmail = 'customer@example.com'; @@ -1519,8 +1548,6 @@ function ($string) { $this->customerSecure->expects($this->any())->method('setPasswordHash')->willReturn(null); $this->sessionManager->expects($this->atLeastOnce())->method('destroy'); - $this->sessionManager->expects($this->atLeastOnce())->method('start'); - $this->sessionManager->expects($this->atLeastOnce())->method('writeClose'); $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) ->disableOriginalConstructor() @@ -1720,7 +1747,7 @@ public function testCreateAccountWithPasswordHashForGuest() $customerMock->expects($this->exactly(3)) ->method('getStoreId') ->willReturn(null); - $customerMock->expects($this->exactly(2)) + $customerMock->expects($this->exactly(3)) ->method('getWebsiteId') ->willReturn(null); $customerMock->expects($this->once()) @@ -1752,6 +1779,9 @@ public function testCreateAccountWithPasswordHashForGuest() $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); } + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ public function testCreateAccountWithPasswordHashWithCustomerAddresses() { $websiteId = 1; @@ -1846,6 +1876,19 @@ public function testCreateAccountWithPasswordHashWithCustomerAddresses() ->expects($this->atLeastOnce()) ->method('getStore') ->willReturn($store); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); $this->assertSame($customer, $this->accountManagement->createAccountWithPasswordHash($customer, $hash)); } @@ -1976,4 +2019,39 @@ public function testCreateAccountUnexpectedValueException(): void $this->accountManagement->createAccount($customer); } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testCreateAccountWithStoreNotInWebsite() + { + $storeId = 1; + $websiteId = 1; + $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; + $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) + ->getMockForAbstractClass(); + $customerMock->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(null); + $customerMock->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customerMock->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $this->share + ->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); + $website->expects($this->once()) + ->method('getStoreIds') + ->willReturn([2, 3]); + $this->storeManager + ->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); + } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php b/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php index 9ab35f2d301d4..d8148543a55db 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Address/Validator/CountryTest.php @@ -161,7 +161,13 @@ public function validateDataProvider() 'region_id2' => [ array_merge($data, ['country_id' => $countryId, 'region_id' => 2]), [$countryId++], - [1], + [], + [], + ], + 'region_id3' => [ + array_merge($data, ['country_id' => $countryId, 'region_id' => 2]), + [$countryId++], + [1, 3], ['Invalid value of "2" provided for the regionId field.'], ], 'validated' => [ diff --git a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php index f3070c46ebb7b..50c21379054bf 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Customer/DataProviderTest.php @@ -1269,7 +1269,7 @@ public function testGetDataWithVisibleAttributesWithAccountEdit() $helper = new ObjectManager($this); $context = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContextInterface::class) ->setMethods(['getRequestParam']) - ->getMockforAbstractClass(); + ->getMockForAbstractClass(); $context->expects($this->any()) ->method('getRequestParam') ->with('request-field-name') diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataHydratorTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataHydratorTest.php index ec9831dde081e..248cae999065c 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataHydratorTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataHydratorTest.php @@ -205,7 +205,7 @@ public function testExtract() ->method('buildOutputDataArray') ->with( $this->attributeMetadataMock, - AttributeMetadataInterface::class + AttributeMetadata::class ) ->willReturn($data); $this->assertSame( diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/TextTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/TextTest.php index b95987cba1dcf..7987bdc79ed98 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/TextTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/TextTest.php @@ -5,8 +5,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Model\Metadata\Form; +use Magento\Customer\Api\Data\ValidationRuleInterface; use Magento\Customer\Model\Metadata\Form\Text; class TextTest extends AbstractFormTestCase @@ -14,6 +16,9 @@ class TextTest extends AbstractFormTestCase /** @var \Magento\Framework\Stdlib\StringUtils */ protected $stringHelper; + /** + * {@inheritDoc} + */ protected function setUp() { parent::setUp(); @@ -111,7 +116,7 @@ public function validateValueRequiredDataProvider() */ public function testValidateValueLength($value, $expected) { - $minTextLengthRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $minTextLengthRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); @@ -122,7 +127,7 @@ public function testValidateValueLength($value, $expected) ->method('getValue') ->will($this->returnValue(4)); - $maxTextLengthRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $maxTextLengthRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); @@ -133,7 +138,19 @@ public function testValidateValueLength($value, $expected) ->method('getValue') ->will($this->returnValue(8)); + $inputValidationRule = $this->getMockBuilder(ValidationRuleInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getName', 'getValue']) + ->getMockForAbstractClass(); + $inputValidationRule->expects($this->any()) + ->method('getName') + ->will($this->returnValue('input_validation')); + $inputValidationRule->expects($this->any()) + ->method('getValue') + ->will($this->returnValue('other')); + $validationRules = [ + 'input_validation' => $inputValidationRule, 'min_text_length' => $minTextLengthRule, 'max_text_length' => $maxTextLengthRule, ]; diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupRepositoryTest.php index 98cf8c212a784..9e8440b500989 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupRepositoryTest.php @@ -38,6 +38,11 @@ class GroupRepositoryTest extends \PHPUnit\Framework\TestCase */ protected $group; + /** + * @var \Magento\Customer\Api\Data\GroupInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $factoryCreatedGroup; + /** * @var \Magento\Customer\Model\ResourceModel\Group|\PHPUnit_Framework_MockObject_MockObject */ @@ -153,6 +158,12 @@ private function setupGroupObjects() 'group', false ); + $this->factoryCreatedGroup = $this->getMockForAbstractClass( + \Magento\Customer\Api\Data\GroupInterface::class, + [], + 'group', + false + ); $this->groupResourceModel = $this->createMock(\Magento\Customer\Model\ResourceModel\Group::class); } @@ -162,16 +173,22 @@ public function testSave() $groupId = 0; $taxClass = $this->getMockForAbstractClass(\Magento\Tax\Api\Data\TaxClassInterface::class, [], '', false); + $extensionAttributes = $this->getMockForAbstractClass( + \Magento\Customer\Api\Data\GroupExtensionInterface::class + ); - $this->group->expects($this->once()) + $this->group->expects($this->atLeastOnce()) ->method('getCode') ->willReturn('Code'); $this->group->expects($this->atLeastOnce()) ->method('getId') ->willReturn($groupId); - $this->group->expects($this->once()) + $this->group->expects($this->atLeastOnce()) ->method('getTaxClassId') ->willReturn(17); + $this->group->expects($this->atLeastOnce()) + ->method('getExtensionAttributes') + ->willReturn($extensionAttributes); $this->groupModel->expects($this->atLeastOnce()) ->method('getId') @@ -185,22 +202,33 @@ public function testSave() $this->groupModel->expects($this->atLeastOnce()) ->method('getTaxClassName') ->willReturn('Tax class name'); - $this->group->expects($this->once()) + + $this->factoryCreatedGroup->expects($this->once()) ->method('setId') ->with($groupId) ->willReturnSelf(); - $this->group->expects($this->once()) + $this->factoryCreatedGroup->expects($this->once()) ->method('setCode') ->with('Code') ->willReturnSelf(); - $this->group->expects($this->once()) + $this->factoryCreatedGroup->expects($this->once()) ->method('setTaxClassId') ->with(234) ->willReturnSelf(); - $this->group->expects($this->once()) + $this->factoryCreatedGroup->expects($this->once()) ->method('setTaxClassName') ->with('Tax class name') ->willReturnSelf(); + $this->factoryCreatedGroup->expects($this->once()) + ->method('setExtensionAttributes') + ->with($extensionAttributes) + ->willReturnSelf(); + $this->factoryCreatedGroup->expects($this->atLeastOnce()) + ->method('getCode') + ->willReturn('Code'); + $this->factoryCreatedGroup->expects($this->atLeastOnce()) + ->method('getTaxClassId') + ->willReturn(17); $this->taxClassRepository->expects($this->once()) ->method('get') @@ -229,9 +257,12 @@ public function testSave() ->with($groupId); $this->groupDataFactory->expects($this->once()) ->method('create') - ->willReturn($this->group); + ->willReturn($this->factoryCreatedGroup); + + $updatedGroup = $this->model->save($this->group); - $this->assertSame($this->group, $this->model->save($this->group)); + $this->assertSame($this->group->getCode(), $updatedGroup->getCode()); + $this->assertSame($this->group->getTaxClassId(), $updatedGroup->getTaxClassId()); } /** diff --git a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php index 7a6807562f906..7efc61af800d3 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php @@ -52,6 +52,9 @@ class SessionTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * @return void + */ protected function setUp() { $this->_storageMock = $this->createPartialMock( @@ -82,6 +85,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testSetCustomerAsLoggedIn() { $customer = $this->createMock(\Magento\Customer\Model\Customer::class); @@ -102,6 +108,9 @@ public function testSetCustomerAsLoggedIn() $this->assertSame($customer, $this->_model->getCustomer()); } + /** + * @return void + */ public function testSetCustomerDataAsLoggedIn() { $customer = $this->createMock(\Magento\Customer\Model\Customer::class); @@ -126,27 +135,36 @@ public function testSetCustomerDataAsLoggedIn() $this->assertSame($customer, $this->_model->getCustomer()); } + /** + * @return void + */ public function testAuthenticate() { $urlMock = $this->createMock(\Magento\Framework\Url::class); $urlMock->expects($this->exactly(2)) ->method('getUrl') - ->will($this->returnValue('')); + ->willReturn(''); $urlMock->expects($this->once()) ->method('getRebuiltUrl') - ->will($this->returnValue('')); - $this->urlFactoryMock->expects($this->exactly(3)) + ->willReturn(''); + $this->urlFactoryMock->expects($this->exactly(4)) ->method('create') - ->will($this->returnValue($urlMock)); + ->willReturn($urlMock); + $urlMock->expects($this->once()) + ->method('getUseSession') + ->willReturn(false); $this->responseMock->expects($this->once()) ->method('setRedirect') ->with('') - ->will($this->returnValue('')); + ->willReturn(''); $this->assertFalse($this->_model->authenticate()); } + /** + * @return void + */ public function testLoginById() { $customerId = 1; @@ -162,7 +180,7 @@ public function testLoginById() } /** - * @param $customerId + * @param int $customerId * @return \PHPUnit_Framework_MockObject_MockObject */ protected function prepareLoginDataMock($customerId) @@ -239,6 +257,9 @@ public function getIsLoggedInDataProvider() ]; } + /** + * @return void + */ public function testSetCustomerRemovesFlagThatShowsIfCustomerIsEmulated() { $customerMock = $this->createMock(\Magento\Customer\Model\Customer::class); diff --git a/app/code/Magento/Customer/etc/acl.xml b/app/code/Magento/Customer/etc/acl.xml index a500608e1cdf5..e8e6219bef4fe 100644 --- a/app/code/Magento/Customer/etc/acl.xml +++ b/app/code/Magento/Customer/etc/acl.xml @@ -12,6 +12,7 @@ <resource id="Magento_Customer::customer" title="Customers" translate="title" sortOrder="40"> <resource id="Magento_Customer::manage" title="All Customers" translate="title" sortOrder="10" /> <resource id="Magento_Customer::online" title="Now Online" translate="title" sortOrder="20" /> + <resource id="Magento_Customer::group" title="Customer Groups" translate="title" sortOrder="30" /> </resource> <resource id="Magento_Backend::stores"> <resource id="Magento_Backend::stores_settings"> @@ -19,10 +20,7 @@ <resource id="Magento_Customer::config_customer" title="Customers Section" translate="title" sortOrder="50" /> </resource> </resource> - <resource id="Magento_Backend::stores_other_settings"> - <resource id="Magento_Customer::group" title="Customer Groups" translate="title" sortOrder="10" /> - </resource> - </resource> + </resource> </resource> </resources> </acl> diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 31e968de14d99..86e5852d67aeb 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -26,7 +26,7 @@ </group> <group id="create_account" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Create New Account Options</label> - <field id="auto_group_assign" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="auto_group_assign" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Enable Automatic Assignment to Customer Group</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> @@ -170,7 +170,7 @@ <comment>Use 0 to disable account locking.</comment> <frontend_class>required-entry validate-digits</frontend_class> </field> - <field id="lockout_threshold" translate="label" sortOrder="80" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="lockout_threshold" translate="label comment" sortOrder="80" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Lockout Time (minutes)</label> <comment>Account will be unlocked after provided time.</comment> <frontend_class>required-entry validate-digits</frontend_class> @@ -272,16 +272,16 @@ </group> <group id="address_templates" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Address Templates</label> - <field id="text" type="textarea" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="text" translate="label" type="textarea" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Text</label> </field> - <field id="oneline" type="textarea" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="oneline" translate="label" type="textarea" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Text One Line</label> </field> - <field id="html" type="textarea" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="html" translate="label" type="textarea" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>HTML</label> </field> - <field id="pdf" type="textarea" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="pdf" translate="label" type="textarea" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>PDF</label> </field> </group> diff --git a/app/code/Magento/Customer/etc/db_schema_whitelist.json b/app/code/Magento/Customer/etc/db_schema_whitelist.json index f0150bccfe84e..4aada8f0d81fe 100644 --- a/app/code/Magento/Customer/etc/db_schema_whitelist.json +++ b/app/code/Magento/Customer/etc/db_schema_whitelist.json @@ -1,349 +1,349 @@ { - "customer_entity": { - "column": { - "entity_id": true, - "website_id": true, - "email": true, - "group_id": true, - "increment_id": true, - "store_id": true, - "created_at": true, - "updated_at": true, - "is_active": true, - "disable_auto_group_change": true, - "created_in": true, - "prefix": true, - "firstname": true, - "middlename": true, - "lastname": true, - "suffix": true, - "dob": true, - "password_hash": true, - "rp_token": true, - "rp_token_created_at": true, - "default_billing": true, - "default_shipping": true, - "taxvat": true, - "confirmation": true, - "gender": true, - "failures_num": true, - "first_failure": true, - "lock_expired": true, - "lock_expires": true - }, - "index": { - "CUSTOMER_ENTITY_STORE_ID": true, - "CUSTOMER_ENTITY_WEBSITE_ID": true, - "CUSTOMER_ENTITY_FIRSTNAME": true, - "CUSTOMER_ENTITY_LASTNAME": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_STORE_ID_STORE_STORE_ID": true, - "CUSTOMER_ENTITY_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "CUSTOMER_ENTITY_EMAIL_WEBSITE_ID": true - } - }, - "customer_address_entity": { - "column": { - "entity_id": true, - "increment_id": true, - "parent_id": true, - "created_at": true, - "updated_at": true, - "is_active": true, - "city": true, - "company": true, - "country_id": true, - "fax": true, - "firstname": true, - "lastname": true, - "middlename": true, - "postcode": true, - "prefix": true, - "region": true, - "region_id": true, - "street": true, - "suffix": true, - "telephone": true, - "vat_id": true, - "vat_is_valid": true, - "vat_request_date": true, - "vat_request_id": true, - "vat_request_success": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ADDRESS_ENTITY_PARENT_ID_CUSTOMER_ENTITY_ENTITY_ID": true - } - }, - "customer_address_entity_datetime": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_DATETIME_ATTRIBUTE_ID": true, - "CUSTOMER_ADDRESS_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_ADDR_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_ADDR_ENTT_DTIME_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, - "CUSTOMER_ADDRESS_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_address_entity_decimal": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ATTRIBUTE_ID": true, - "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_ADDR_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_ADDR_ENTT_DEC_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, - "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_address_entity_int": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_INT_ATTRIBUTE_ID": true, - "CUSTOMER_ADDRESS_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_ADDR_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_ADDR_ENTT_INT_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, - "CUSTOMER_ADDRESS_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_address_entity_text": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_TEXT_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_ADDR_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_ADDR_ENTT_TEXT_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, - "CUSTOMER_ADDRESS_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_address_entity_varchar": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID": true, - "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_ADDR_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_ADDR_ENTT_VCHR_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, - "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_entity_datetime": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ENTITY_DATETIME_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_DATETIME_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_entity_decimal": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ENTITY_DECIMAL_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_DECIMAL_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_entity_int": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ENTITY_INT_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_INT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_INT_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_entity_text": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ENTITY_TEXT_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_TEXT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_TEXT_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CUSTOMER_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_entity_varchar": { - "column": { - "value_id": true, - "attribute_id": true, - "entity_id": true, - "value": true - }, - "index": { - "CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID": true - } - }, - "customer_group": { - "column": { - "customer_group_id": true, - "customer_group_code": true, - "tax_class_id": true - }, - "constraint": { - "PRIMARY": true - } - }, - "customer_eav_attribute": { - "column": { - "attribute_id": true, - "is_visible": true, - "input_filter": true, - "multiline_count": true, - "validate_rules": true, - "is_system": true, - "sort_order": true, - "data_model": true, - "is_used_in_grid": true, - "is_visible_in_grid": true, - "is_filterable_in_grid": true, - "is_searchable_in_grid": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true - } - }, - "customer_form_attribute": { - "column": { - "form_code": true, - "attribute_id": true - }, - "index": { - "CUSTOMER_FORM_ATTRIBUTE_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_FORM_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true - } - }, - "customer_eav_attribute_website": { - "column": { - "attribute_id": true, - "website_id": true, - "is_visible": true, - "is_required": true, - "default_value": true, - "multiline_count": true - }, - "index": { - "CUSTOMER_EAV_ATTRIBUTE_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "CSTR_EAV_ATTR_WS_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "CSTR_EAV_ATTR_WS_WS_ID_STORE_WS_WS_ID": true - } - }, - "customer_visitor": { - "column": { - "visitor_id": true, - "customer_id": true, - "session_id": true, - "last_visit_at": true - }, - "index": { - "CUSTOMER_VISITOR_CUSTOMER_ID": true, - "CUSTOMER_VISITOR_LAST_VISIT_AT": true - }, - "constraint": { - "PRIMARY": true - } - }, - "customer_log": { - "column": { - "log_id": true, - "customer_id": true, - "last_login_at": true, - "last_logout_at": true - }, - "constraint": { - "PRIMARY": true, - "CUSTOMER_LOG_CUSTOMER_ID": true + "customer_entity": { + "column": { + "entity_id": true, + "website_id": true, + "email": true, + "group_id": true, + "increment_id": true, + "store_id": true, + "created_at": true, + "updated_at": true, + "is_active": true, + "disable_auto_group_change": true, + "created_in": true, + "prefix": true, + "firstname": true, + "middlename": true, + "lastname": true, + "suffix": true, + "dob": true, + "password_hash": true, + "rp_token": true, + "rp_token_created_at": true, + "default_billing": true, + "default_shipping": true, + "taxvat": true, + "confirmation": true, + "gender": true, + "failures_num": true, + "first_failure": true, + "lock_expired": true, + "lock_expires": true + }, + "index": { + "CUSTOMER_ENTITY_STORE_ID": true, + "CUSTOMER_ENTITY_WEBSITE_ID": true, + "CUSTOMER_ENTITY_FIRSTNAME": true, + "CUSTOMER_ENTITY_LASTNAME": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_STORE_ID_STORE_STORE_ID": true, + "CUSTOMER_ENTITY_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "CUSTOMER_ENTITY_EMAIL_WEBSITE_ID": true + } + }, + "customer_address_entity": { + "column": { + "entity_id": true, + "increment_id": true, + "parent_id": true, + "created_at": true, + "updated_at": true, + "is_active": true, + "city": true, + "company": true, + "country_id": true, + "fax": true, + "firstname": true, + "lastname": true, + "middlename": true, + "postcode": true, + "prefix": true, + "region": true, + "region_id": true, + "street": true, + "suffix": true, + "telephone": true, + "vat_id": true, + "vat_is_valid": true, + "vat_request_date": true, + "vat_request_id": true, + "vat_request_success": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ADDRESS_ENTITY_PARENT_ID_CUSTOMER_ENTITY_ENTITY_ID": true + } + }, + "customer_address_entity_datetime": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_DATETIME_ATTRIBUTE_ID": true, + "CUSTOMER_ADDRESS_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_ADDR_ENTT_DTIME_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_ADDR_ENTT_DTIME_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, + "CUSTOMER_ADDRESS_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_address_entity_decimal": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ATTRIBUTE_ID": true, + "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_ADDR_ENTT_DEC_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_ADDR_ENTT_DEC_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, + "CUSTOMER_ADDRESS_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_address_entity_int": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_INT_ATTRIBUTE_ID": true, + "CUSTOMER_ADDRESS_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_ADDR_ENTT_INT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_ADDR_ENTT_INT_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, + "CUSTOMER_ADDRESS_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_address_entity_text": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_TEXT_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_ADDR_ENTT_TEXT_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_ADDR_ENTT_TEXT_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, + "CUSTOMER_ADDRESS_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_address_entity_varchar": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ATTRIBUTE_ID": true, + "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_ADDR_ENTT_VCHR_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_ADDR_ENTT_VCHR_ENTT_ID_CSTR_ADDR_ENTT_ENTT_ID": true, + "CUSTOMER_ADDRESS_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_entity_datetime": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ENTITY_DATETIME_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_DATETIME_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CUSTOMER_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_entity_decimal": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ENTITY_DECIMAL_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_DECIMAL_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CUSTOMER_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_entity_int": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ENTITY_INT_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_INT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_INT_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CUSTOMER_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_entity_text": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ENTITY_TEXT_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_TEXT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_TEXT_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CUSTOMER_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_entity_varchar": { + "column": { + "value_id": true, + "attribute_id": true, + "entity_id": true, + "value": true + }, + "index": { + "CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_ENTITY_VARCHAR_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "CUSTOMER_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID": true + } + }, + "customer_group": { + "column": { + "customer_group_id": true, + "customer_group_code": true, + "tax_class_id": true + }, + "constraint": { + "PRIMARY": true + } + }, + "customer_eav_attribute": { + "column": { + "attribute_id": true, + "is_visible": true, + "input_filter": true, + "multiline_count": true, + "validate_rules": true, + "is_system": true, + "sort_order": true, + "data_model": true, + "is_used_in_grid": true, + "is_visible_in_grid": true, + "is_filterable_in_grid": true, + "is_searchable_in_grid": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_EAV_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true + } + }, + "customer_form_attribute": { + "column": { + "form_code": true, + "attribute_id": true + }, + "index": { + "CUSTOMER_FORM_ATTRIBUTE_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_FORM_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true + } + }, + "customer_eav_attribute_website": { + "column": { + "attribute_id": true, + "website_id": true, + "is_visible": true, + "is_required": true, + "default_value": true, + "multiline_count": true + }, + "index": { + "CUSTOMER_EAV_ATTRIBUTE_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "CSTR_EAV_ATTR_WS_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "CSTR_EAV_ATTR_WS_WS_ID_STORE_WS_WS_ID": true + } + }, + "customer_visitor": { + "column": { + "visitor_id": true, + "customer_id": true, + "session_id": true, + "last_visit_at": true + }, + "index": { + "CUSTOMER_VISITOR_CUSTOMER_ID": true, + "CUSTOMER_VISITOR_LAST_VISIT_AT": true + }, + "constraint": { + "PRIMARY": true + } + }, + "customer_log": { + "column": { + "log_id": true, + "customer_id": true, + "last_login_at": true, + "last_logout_at": true + }, + "constraint": { + "PRIMARY": true, + "CUSTOMER_LOG_CUSTOMER_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index 717b2b6c67e20..6e8c3dc68ed28 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -75,6 +75,13 @@ <argument name="addressConfig" xsi:type="object">Magento\Customer\Model\Address\Config\Proxy</argument> </arguments> </type> + <type name="Magento\Framework\App\Http\Context"> + <arguments> + <argument name="default" xsi:type="array"> + <item name="customer_group" xsi:type="const">Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID</item> + </argument> + </arguments> + </type> <type name="Magento\Customer\Model\Config\Share"> <arguments> <argument name="customerResource" xsi:type="object">Magento\Customer\Model\ResourceModel\Customer\Proxy</argument> diff --git a/app/code/Magento/Customer/etc/webapi.xml b/app/code/Magento/Customer/etc/webapi.xml index f9ef3a5f88228..c536e26bcc82a 100644 --- a/app/code/Magento/Customer/etc/webapi.xml +++ b/app/code/Magento/Customer/etc/webapi.xml @@ -200,12 +200,6 @@ <resource ref="anonymous"/> </resources> </route> - <route url="/V1/customers/resetPassword" method="POST"> - <service class="Magento\Customer\Api\AccountManagementInterface" method="resetPassword"/> - <resources> - <resource ref="anonymous"/> - </resources> - </route> <route url="/V1/customers/:customerId/confirm" method="GET"> <service class="Magento\Customer\Api\AccountManagementInterface" method="getConfirmationStatus"/> <resources> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml b/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml index 8f65d54f458bc..fd5ecbfa7f277 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml @@ -12,6 +12,9 @@ </referenceBlock> <referenceContainer name="content"> <block class="Magento\Customer\Block\Form\Register" name="customer_form_register" template="Magento_Customer::form/register.phtml"> + <arguments> + <argument name="attribute_data" xsi:type="object">Magento\Customer\Block\DataProviders\AddressAttributeData</argument> + </arguments> <container name="form.additional.info" as="form_additional_info"/> <container name="customer.form.register.fields.before" as="form_fields_before" label="Form Fields Before" htmlTag="div" htmlClass="customer-form-before"/> </block> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml b/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml index 194dfd1bc7d2b..f053805409fe5 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml @@ -17,7 +17,11 @@ </arguments> </referenceBlock> <referenceContainer name="content"> - <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"/> + <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"> + <arguments> + <argument name="attribute_data" xsi:type="object">Magento\Customer\Block\DataProviders\AddressAttributeData</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Customer/view/frontend/templates/account/link/authorization.phtml b/app/code/Magento/Customer/view/frontend/templates/account/link/authorization.phtml index 10223df489e90..24657a6846cae 100644 --- a/app/code/Magento/Customer/view/frontend/templates/account/link/authorization.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/account/link/authorization.phtml @@ -13,7 +13,7 @@ if ($block->isLoggedIn()) { $dataPostParam = sprintf(" data-post='%s'", $block->getPostParams()); } ?> -<li class="authorization-link" data-label="<?= $block->escapeHtmlAttr(__('or')) ?>"> +<li class="authorization-link" data-label="<?= $block->escapeHtml(__('or')) ?>"> <a <?= /* @noEscape */ $block->getLinkAttributes() ?><?= /* @noEscape */ $dataPostParam ?>> <?= $block->escapeHtml($block->getLabel()) ?> </a> diff --git a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml index 1f1f078504524..086b7423befec 100644 --- a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml @@ -42,13 +42,13 @@ <?php $_streetValidationClass = $this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('street'); ?> <div class="field street required"> <label for="street_1" class="label"> - <span><?= $block->escapeHtml(__('Street Address')) ?></span> + <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?></span> </label> <div class="control"> <input type="text" name="street[]" value="<?= $block->escapeHtmlAttr($block->getStreetLine(1)) ?>" - title="<?= $block->escapeHtmlAttr(__('Street Address')) ?>" + title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?>" id="street_1" class="input-text <?= $block->escapeHtmlAttr($_streetValidationClass) ?>"/> <div class="nested"> @@ -74,20 +74,20 @@ <?php if ($this->helper('Magento\Customer\Helper\Address')->isVatAttributeVisible()) : ?> <div class="field taxvat"> <label class="label" for="vat_id"> - <span><?= $block->escapeHtml(__('VAT Number')) ?></span> + <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?></span> </label> <div class="control"> <input type="text" name="vat_id" value="<?= $block->escapeHtmlAttr($block->getAddress()->getVatId()) ?>" - title="<?= $block->escapeHtmlAttr(__('VAT Number')) ?>" + title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('vat_id') ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('vat_id')) ?>" id="vat_id"> </div> </div> <?php endif; ?> <div class="field city required"> - <label class="label" for="city"><span><?= $block->escapeHtml(__('City')) ?></span></label> + <label class="label" for="city"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?></span></label> <div class="control"> <input type="text" name="city" @@ -99,11 +99,11 @@ </div> <div class="field region required"> <label class="label" for="region_id"> - <span><?= $block->escapeHtml(__('State/Province')) ?></span> + <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?></span> </label> <div class="control"> <select id="region_id" name="region_id" - title="<?= $block->escapeHtmlAttr(__('State/Province')) ?>" + title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>" class="validate-select" <?= /* @noEscape */ !$block->getConfig('general/region/display_all') ? ' disabled="disabled"' : '' ?>> <option value=""><?= $block->escapeHtml(__('Please select a region, state or province.')) ?></option> </select> @@ -111,25 +111,25 @@ id="region" name="region" value="<?= $block->escapeHtmlAttr($block->getRegion()) ?>" - title="<?= $block->escapeHtmlAttr(__('State/Province')) ?>" - class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('region')) ?>"<?= !$block->getConfig('general/region/display_all') ? ' disabled="disabled"' : '' ?>/> + title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>" + class="input-text validate-not-number-first <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('region')) ?>"<?= !$block->getConfig('general/region/display_all') ? ' disabled="disabled"' : '' ?>/> </div> </div> <div class="field zip required"> <label class="label" for="zip"> - <span><?= $block->escapeHtml(__('Zip/Postal Code')) ?></span> + <span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?></span> </label> <div class="control"> <input type="text" name="postcode" value="<?= $block->escapeHtmlAttr($block->getAddress()->getPostcode()) ?>" - title="<?= $block->escapeHtmlAttr(__('Zip/Postal Code')) ?>" + title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?>" id="zip" class="input-text validate-zip-international <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('postcode')) ?>"> </div> </div> <div class="field country required"> - <label class="label" for="country"><span><?= $block->escapeHtml(__('Country')) ?></span></label> + <label class="label" for="country"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('country_id') ?></span></label> <div class="control"> <?= $block->getCountryHtmlSelect() ?> </div> diff --git a/app/code/Magento/Customer/view/frontend/templates/form/confirmation.phtml b/app/code/Magento/Customer/view/frontend/templates/form/confirmation.phtml index 0de3d94334970..8edb21ac8e1e9 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/confirmation.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/confirmation.phtml @@ -14,7 +14,7 @@ <div class="field email required"> <label for="email_address" class="label"><span><?= $block->escapeHtml(__('Email')) ?></span></label> <div class="control"> - <input type="email" name="email" id="email_address" class="input-text" value="<?= $block->escapeHtmlAttr($block->getEmail()) ?>" data-validate="{required:true, 'validate-email':true}"> + <input type="email" name="email" id="email_address" class="input-text" value="<?= $block->escapeHtmlAttr($block->getEmail()) ?>" data-validate="{required:true, 'validate-email':true}" data-mage-init='{"mage/trim-input":{}}'> </div> </div> </fieldset> diff --git a/app/code/Magento/Customer/view/frontend/templates/form/login.phtml b/app/code/Magento/Customer/view/frontend/templates/form/login.phtml index 2d44dde215139..d85e76e4cbc3f 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/login.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/login.phtml @@ -19,7 +19,7 @@ id="login-form" data-mage-init='{"validation":{}}'> <?= $block->getBlockHtml('formkey') ?> - <fieldset class="fieldset login" data-hasrequired="<?= $block->escapeHtmlAttr(__('* Required Fields')) ?>"> + <fieldset class="fieldset login" data-hasrequired="<?= $block->escapeHtml(__('* Required Fields')) ?>"> <div class="field note"><?= $block->escapeHtml(__('If you have an account, sign in with your email address.')) ?></div> <div class="field email required"> <label class="label" for="email"><span><?= $block->escapeHtml(__('Email')) ?></span></label> diff --git a/app/code/Magento/Customer/view/frontend/templates/form/register.phtml b/app/code/Magento/Customer/view/frontend/templates/form/register.phtml index 6cdb8fc44f665..bb86c9f453dfb 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/register.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/register.phtml @@ -65,9 +65,9 @@ <?php $_streetValidationClass = $this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('street'); ?> <div class="field street required"> - <label for="street_1" class="label"><span><?= $block->escapeHtml(__('Street Address')) ?></span></label> + <label for="street_1" class="label"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?></span></label> <div class="control"> - <input type="text" name="street[]" value="<?= $block->escapeHtmlAttr($block->getFormData()->getStreet(0)) ?>" title="<?= $block->escapeHtmlAttr(__('Street Address')) ?>" id="street_1" class="input-text <?= $block->escapeHtmlAttr($_streetValidationClass) ?>"> + <input type="text" name="street[]" value="<?= $block->escapeHtmlAttr($block->getFormData()->getStreet(0)) ?>" title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('street') ?>" id="street_1" class="input-text <?= $block->escapeHtmlAttr($_streetValidationClass) ?>"> <div class="nested"> <?php $_streetValidationClass = trim(str_replace('required-entry', '', $_streetValidationClass)); ?> <?php for ($_i = 2, $_n = $this->helper('Magento\Customer\Helper\Address')->getStreetLines(); $_i <= $_n; $_i++): ?> @@ -85,31 +85,31 @@ </div> <div class="field required"> - <label for="city" class="label"><span><?= $block->escapeHtml(__('City')) ?></span></label> + <label for="city" class="label"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?></span></label> <div class="control"> - <input type="text" name="city" value="<?= $block->escapeHtmlAttr($block->getFormData()->getCity()) ?>" title="<?= $block->escapeHtmlAttr(__('City')) ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('city')) ?>" id="city"> + <input type="text" name="city" value="<?= $block->escapeHtmlAttr($block->getFormData()->getCity()) ?>" title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('city') ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('city')) ?>" id="city"> </div> </div> <div class="field region required"> - <label for="region_id" class="label"><span><?= $block->escapeHtml(__('State/Province')) ?></span></label> + <label for="region_id" class="label"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?></span></label> <div class="control"> - <select id="region_id" name="region_id" title="<?= $block->escapeHtmlAttr(__('State/Province')) ?>" class="validate-select" style="display:none;"> + <select id="region_id" name="region_id" title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>" class="validate-select" style="display:none;"> <option value=""><?= $block->escapeHtml(__('Please select a region, state or province.')) ?></option> </select> - <input type="text" id="region" name="region" value="<?= $block->escapeHtml($block->getRegion()) ?>" title="<?= $block->escapeHtmlAttr(__('State/Province')) ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('region')) ?>" style="display:none;"> + <input type="text" id="region" name="region" value="<?= $block->escapeHtml($block->getRegion()) ?>" title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('region') ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('region')) ?>" style="display:none;"> </div> </div> <div class="field zip required"> - <label for="zip" class="label"><span><?= $block->escapeHtml(__('Zip/Postal Code')) ?></span></label> + <label for="zip" class="label"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?></span></label> <div class="control"> - <input type="text" name="postcode" value="<?= $block->escapeHtmlAttr($block->getFormData()->getPostcode()) ?>" title="<?= $block->escapeHtmlAttr(__('Zip/Postal Code')) ?>" id="zip" class="input-text validate-zip-international <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('postcode')) ?>"> + <input type="text" name="postcode" value="<?= $block->escapeHtmlAttr($block->getFormData()->getPostcode()) ?>" title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?>" id="zip" class="input-text validate-zip-international <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('postcode')) ?>"> </div> </div> <div class="field country required"> - <label for="country" class="label"><span><?= $block->escapeHtml(__('Country')) ?></span></label> + <label for="country" class="label"><span><?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('country_id') ?></span></label> <div class="control"> <?= $block->getCountryHtmlSelect() ?> </div> diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index c4672c48e1f4a..ec21e45da6852 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -27,7 +27,8 @@ define([ //TODO: remove global change, in this case made for initNamespaceStorage $.cookieStorage.setConf({ - path: '/' + path: '/', + expires: 1 }); storage = $.initNamespaceStorage('mage-cache-storage').localStorage; diff --git a/app/code/Magento/Customer/view/frontend/web/js/password-strength-indicator.js b/app/code/Magento/Customer/view/frontend/web/js/password-strength-indicator.js index be2e0aedfe4bb..89d9b320c049d 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/password-strength-indicator.js +++ b/app/code/Magento/Customer/view/frontend/web/js/password-strength-indicator.js @@ -31,7 +31,7 @@ define([ this.options.cache.label = $(this.options.passwordStrengthMeterLabelSelector, this.element); // We need to look outside the module for backward compatibility, since someone can already use the module. - // @todo Narrow this selector in 2.3 so it doesn't accidentally finds the the email field from the + // @todo Narrow this selector in 2.3 so it doesn't accidentally finds the email field from the // newsletter email field or any other "email" field. this.options.cache.email = $(this.options.formSelector).find(this.options.emailSelector); this._bind(); diff --git a/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json b/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 9b90f06de29c6..0000000000000 --- a/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-customer-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CustomerGraphQl/Test/Mftf/composer.json b/app/code/Magento/CustomerGraphQl/Test/Mftf/composer.json deleted file mode 100644 index b173084a84d10..0000000000000 --- a/app/code/Magento/CustomerGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-customer-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index 687826cff001d..91b7ef1f9be15 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -19,9 +19,9 @@ type Customer @doc(description: "Customer defines the customer name and address dob: String @doc(description: "The customer's date of birth") taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") id: Int @doc(description: "The ID assigned to the customer") - is_subscribed: Boolean @doc(description: "An array containing the customer's shipping and billing addresses") - addresses: [CustomerAddress] @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") -} + is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") + addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses") +} type CustomerAddress @doc(description: "CustomerAddress contains detailed information about a customer's billing and shipping addresses"){ id: Int @doc(description: "The ID assigned to the address object") diff --git a/app/code/Magento/CustomerImportExport/Model/Export/Customer.php b/app/code/Magento/CustomerImportExport/Model/Export/Customer.php index 14f0ae324e0a4..9ea9e1e5bd93d 100644 --- a/app/code/Magento/CustomerImportExport/Model/Export/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Export/Customer.php @@ -15,7 +15,7 @@ */ class Customer extends \Magento\ImportExport\Model\Export\Entity\AbstractEav { - /**#@+ + /** * Permanent column names. * * Names that begins with underscore is not an attribute. This name convention is for @@ -27,23 +27,19 @@ class Customer extends \Magento\ImportExport\Model\Export\Entity\AbstractEav const COLUMN_STORE = '_store'; - /**#@-*/ - - /**#@+ + /** * Attribute collection name */ const ATTRIBUTE_COLLECTION_NAME = \Magento\Customer\Model\ResourceModel\Attribute\Collection::class; - /**#@-*/ - - /**#@+ + /** * XML path to page size parameter */ const XML_PATH_PAGE_SIZE = 'export/customer_page_size/customer'; - /**#@-*/ - - /**#@-*/ + /** + * @var array + */ protected $_attributeOverrides = [ 'created_at' => ['backend_type' => 'datetime'], 'reward_update_notification' => ['source_model' => \Magento\Eav\Model\Entity\Attribute\Source\Boolean::class], diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index e5cc543db6aac..ab940c9e84533 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -593,6 +593,18 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) if (in_array($attributeCode, $this->_ignoredAttributes)) { continue; } + + $isFieldRequired = $attributeParams['is_required']; + $isFieldNotSetAndCustomerDoesNotExist = + !isset($rowData[$attributeCode]) && !$this->_getCustomerId($email, $website); + $isFieldSetAndTrimmedValueIsEmpty + = isset($rowData[$attributeCode]) && '' === trim($rowData[$attributeCode]); + + if ($isFieldRequired && ($isFieldNotSetAndCustomerDoesNotExist || $isFieldSetAndTrimmedValueIsEmpty)) { + $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); + continue; + } + if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { $this->isAttributeValid( $attributeCode, @@ -603,8 +615,6 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) ? $this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR] : Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR ); - } elseif ($attributeParams['is_required'] && !$this->_getCustomerId($email, $website)) { - $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); } } } diff --git a/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json b/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json deleted file mode 100644 index adddda9ff7370..0000000000000 --- a/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-customer-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Deploy/Test/Mftf/composer.json b/app/code/Magento/Deploy/Test/Mftf/composer.json deleted file mode 100644 index 7e18c4d1d1222..0000000000000 --- a/app/code/Magento/Deploy/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-deploy", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php b/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php index 776bef99e7960..d283e693b8de6 100644 --- a/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php +++ b/app/code/Magento/Developer/Console/Command/TablesWhitelistGenerateCommand.php @@ -42,11 +42,16 @@ class TablesWhitelistGenerateCommand extends Command */ private $jsonPersistor; + /** + * @var array + */ + private $primaryDbSchema; + /** * @param ComponentRegistrar $componentRegistrar * @param ReaderComposite $readerComposite * @param JsonPersistor $jsonPersistor - * @param null $name + * @param string|null $name */ public function __construct( ComponentRegistrar $componentRegistrar, @@ -104,18 +109,21 @@ private function persistModule($moduleName) if (file_exists($whiteListFileName)) { $content = json_decode(file_get_contents($whiteListFileName), true); } - $newContent = $this->readerComposite->read($moduleName); - //Do merge between what we have before, and what we have now. + $newContent = $this->filterPrimaryTables($this->readerComposite->read($moduleName)); + + //Do merge between what we have before, and what we have now and filter to only certain attributes. $content = array_replace_recursive( $content, - $this->selectNamesFromContent($newContent) + $this->filterAttributeNames($newContent) ); - $this->jsonPersistor->persist($content, $whiteListFileName); + if (!empty($content)) { + $this->jsonPersistor->persist($content, $whiteListFileName); + } } /** - * {@inheritdoc} + * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -144,7 +152,7 @@ protected function execute(InputInterface $input, OutputInterface $output) * @param array $content * @return array */ - private function selectNamesFromContent(array $content) + private function filterAttributeNames(array $content) { $names = []; $types = ['column', 'index', 'constraint']; @@ -163,4 +171,35 @@ private function selectNamesFromContent(array $content) return $names; } + + /** + * Load db_schema content from the primary scope app/etc/db_schema.xml. + * + * @return array + */ + private function getPrimaryDbSchema() + { + if (!$this->primaryDbSchema) { + $this->primaryDbSchema = $this->readerComposite->read('primary'); + } + return $this->primaryDbSchema; + } + + /** + * Filter tables from module db_schema.xml as they should not contain the primary system tables. + * + * @param array $moduleDbSchema + * @return array + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + private function filterPrimaryTables(array $moduleDbSchema) + { + $primaryDbSchema = $this->getPrimaryDbSchema(); + if (isset($moduleDbSchema['table']) && isset($primaryDbSchema['table'])) { + foreach ($primaryDbSchema['table'] as $tableNameKey => $tableContents) { + unset($moduleDbSchema['table'][$tableNameKey]); + } + } + return $moduleDbSchema; + } } diff --git a/app/code/Magento/Developer/Test/Mftf/composer.json b/app/code/Magento/Developer/Test/Mftf/composer.json deleted file mode 100644 index ebd6230526271..0000000000000 --- a/app/code/Magento/Developer/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-developer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/TablesWhitelistGenerateCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/TablesWhitelistGenerateCommandTest.php index 66fd7e3eec638..5bfc5686b05fb 100644 --- a/app/code/Magento/Developer/Test/Unit/Console/Command/TablesWhitelistGenerateCommandTest.php +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/TablesWhitelistGenerateCommandTest.php @@ -45,6 +45,9 @@ class TablesWhitelistGenerateCommandTest extends \PHPUnit\Framework\TestCase */ private $jsonPersistorMock; + /** + * {@inheritdoc} + */ protected function setUp() { $this->componentRegistrarMock = $this->getMockBuilder(ComponentRegistrar::class) @@ -76,6 +79,47 @@ public function whitelistTableProvider() [ 'moduleName' => 'SomeModule', 'whitelist' => [ + 'primary' => [ + 'table' => + [ + 'patch_list' => + [ + 'column' => + [ + 'patch_id' => + [ + 'type' => 'int', + 'name' => 'patch_id', + 'identity' => 'true', + 'comment' => 'Patch Auto Increment', + ], + 'patch_name' => + [ + 'type' => 'varchar', + 'name' => 'patch_name', + 'length' => '1024', + 'nullable' => 'false', + 'comment' => 'Patch Class Name', + ], + ], + 'constraint' => + [ + 'PRIMARY' => + [ + 'column' => + [ + 'patch_id' => 'patch_id', + ], + 'type' => 'primary', + 'name' => 'PRIMARY', + ], + ], + 'name' => 'patch_list', + 'resource' => 'default', + 'comment' => 'List of data/schema patches', + ], + ], + ], 'SomeModule' => [ 'table' => [ 'first_table' => [ @@ -149,6 +193,47 @@ public function whitelistTableProvider() [ 'moduleName' => false, 'whitelist' => [ + 'primary' => [ + 'table' => + [ + 'patch_list' => + [ + 'column' => + [ + 'patch_id' => + [ + 'type' => 'int', + 'name' => 'patch_id', + 'identity' => 'true', + 'comment' => 'Patch Auto Increment', + ], + 'patch_name' => + [ + 'type' => 'varchar', + 'name' => 'patch_name', + 'length' => '1024', + 'nullable' => 'false', + 'comment' => 'Patch Class Name', + ], + ], + 'constraint' => + [ + 'PRIMARY' => + [ + 'column' => + [ + 'patch_id' => 'patch_id', + ], + 'type' => 'primary', + 'name' => 'PRIMARY', + ], + ], + 'name' => 'patch_list', + 'resource' => 'default', + 'comment' => 'List of data/schema patches', + ], + ], + ], 'SomeModule' => [ 'table' => [ 'first_table' => [ @@ -303,10 +388,14 @@ public function testCommand($moduleName, array $whiteListTables, array $expected $this->componentRegistrarMock->expects(self::once()) ->method('getPaths') ->willReturn(['SomeModule' => 1, 'Module2' => 2]); - $this->readerCompositeMock->expects(self::exactly(2)) + $this->readerCompositeMock->expects(self::exactly(3)) ->method('read') - ->withConsecutive(['SomeModule'], ['Module2']) - ->willReturnOnConsecutiveCalls($whiteListTables['SomeModule'], $whiteListTables['Module2']); + ->withConsecutive(['SomeModule'], ['primary'], ['Module2']) + ->willReturnOnConsecutiveCalls( + $whiteListTables['SomeModule'], + $whiteListTables['primary'], + $whiteListTables['Module2'] + ); $this->jsonPersistorMock->expects(self::exactly(2)) ->method('persist') ->withConsecutive( @@ -320,10 +409,10 @@ public function testCommand($moduleName, array $whiteListTables, array $expected ] ); } else { - $this->readerCompositeMock->expects(self::once()) + $this->readerCompositeMock->expects(self::exactly(2)) ->method('read') - ->with($moduleName) - ->willReturn($whiteListTables['SomeModule']); + ->withConsecutive([$moduleName], ['primary']) + ->willReturnOnConsecutiveCalls($whiteListTables['SomeModule'], $whiteListTables['primary']); $this->jsonPersistorMock->expects(self::once()) ->method('persist') ->with( diff --git a/app/code/Magento/Developer/etc/adminhtml/system.xml b/app/code/Magento/Developer/etc/adminhtml/system.xml index aae9913009837..4ebc45f1a2ca2 100644 --- a/app/code/Magento/Developer/etc/adminhtml/system.xml +++ b/app/code/Magento/Developer/etc/adminhtml/system.xml @@ -6,7 +6,7 @@ */--> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="dev" translate="label"> + <section id="dev"> <group id="front_end_development_workflow" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Frontend Development Workflow</label> <field id="type" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> @@ -25,7 +25,7 @@ <backend_model>Magento\Developer\Model\Config\Backend\AllowedIps</backend_model> </field> </group> - <group id="debug" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="debug" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <field id="debug_logging" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Log to File</label> <comment>Not available in production mode.</comment> diff --git a/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php b/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php index deeb769c808fa..bf94a3b5c89ad 100644 --- a/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php +++ b/app/code/Magento/Dhl/Block/Adminhtml/Unitofmeasure.php @@ -86,7 +86,7 @@ public function _construct() ) ); - $this->setTemplate('unitofmeasure.phtml'); + $this->setTemplate('Magento_Dhl::unitofmeasure.phtml'); } /** diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index 258dcbe9595d4..d997db6ac1a3e 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -232,7 +232,7 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory * @param array $data - * @param \Magento\Dhl\Model\Validator\XmlValidatorFactory $xmlValidatorFactory + * @param \Magento\Dhl\Model\Validator\XmlValidator $xmlValidator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -1263,8 +1263,20 @@ protected function _doShipmentRequest(\Magento\Framework\DataObject $request) * * @param \Magento\Framework\DataObject $request * @return $this|\Magento\Framework\DataObject|boolean + * @deprecated */ public function proccessAdditionalValidation(\Magento\Framework\DataObject $request) + { + return $this->processAdditionalValidation($request); + } + + /** + * Processing additional validation to check is carrier applicable. + * + * @param \Magento\Framework\DataObject $request + * @return $this|\Magento\Framework\DataObject|boolean + */ + public function processAdditionalValidation(\Magento\Framework\DataObject $request) { //Skip by item validation if there is no items in request if (!count($this->getAllItems($request))) { @@ -1958,6 +1970,6 @@ protected function isDutiable($origCountryId, $destCountryId) return self::DHL_CONTENT_TYPE_NON_DOC == $this->getConfigData('content_type') - && !$this->_isDomestic; + || !$this->_isDomestic; } } diff --git a/app/code/Magento/Dhl/Test/Mftf/composer.json b/app/code/Magento/Dhl/Test/Mftf/composer.json deleted file mode 100644 index 0da17d213c84e..0000000000000 --- a/app/code/Magento/Dhl/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-dhl", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml index c0f7e209ad61b..91ed6c6568a70 100644 --- a/app/code/Magento/Dhl/etc/adminhtml/system.xml +++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml @@ -94,7 +94,7 @@ <field id="content_type">N</field> </depends> </field> - <field id="ready_time" type="text" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="ready_time" translate="label comment" type="text" sortOrder="180" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Ready time</label> <comment>Package ready time after order submission (in hours)</comment> </field> diff --git a/app/code/Magento/Directory/Model/AllowedCountries.php b/app/code/Magento/Directory/Model/AllowedCountries.php index 46fa04ba22651..b9f2d829dd1a6 100644 --- a/app/code/Magento/Directory/Model/AllowedCountries.php +++ b/app/code/Magento/Directory/Model/AllowedCountries.php @@ -46,8 +46,8 @@ public function __construct( /** * Retrieve all allowed countries for scope or scopes * - * @param string | null $scopeCode * @param string $scope + * @param string|null $scopeCode * @return array * @since 100.1.2 */ @@ -55,7 +55,7 @@ public function getAllowedCountries( $scope = ScopeInterface::SCOPE_WEBSITE, $scopeCode = null ) { - if (empty($scopeCode)) { + if ($scopeCode === null) { $scopeCode = $this->getDefaultScopeCode($scope); } diff --git a/app/code/Magento/Directory/Model/Config/Source/Country.php b/app/code/Magento/Directory/Model/Config/Source/Country.php index e807f34456e24..a38ac4b62e1ee 100644 --- a/app/code/Magento/Directory/Model/Config/Source/Country.php +++ b/app/code/Magento/Directory/Model/Config/Source/Country.php @@ -20,6 +20,13 @@ class Country implements \Magento\Framework\Option\ArrayInterface */ protected $_countryCollection; + /** + * Options array + * + * @var array + */ + protected $_options; + /** * @param \Magento\Directory\Model\ResourceModel\Country\Collection $countryCollection */ @@ -28,13 +35,6 @@ public function __construct(\Magento\Directory\Model\ResourceModel\Country\Colle $this->_countryCollection = $countryCollection; } - /** - * Options array - * - * @var array - */ - protected $_options; - /** * Return options array * diff --git a/app/code/Magento/Directory/Model/Currency/Import/YahooFinance.php b/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php similarity index 54% rename from app/code/Magento/Directory/Model/Currency/Import/YahooFinance.php rename to app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php index 5f96b8627af65..f52886a14264d 100644 --- a/app/code/Magento/Directory/Model/Currency/Import/YahooFinance.php +++ b/app/code/Magento/Directory/Model/Currency/Import/CurrencyConverterApi.php @@ -3,28 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Directory\Model\Currency\Import; -/** - * Currency rate import model (From http://query.yahooapis.com/) - */ -class YahooFinance extends \Magento\Directory\Model\Currency\Import\AbstractImport +class CurrencyConverterApi extends AbstractImport { - // @codingStandardsIgnoreStart - - // @codingStandardsIgnoreStart - private $currencyConverterUrl = 'http://query.yahooapis.com/v1/public/yql?format=json&q={{YQL_STRING}}&env=store://datatables.org/alltableswithkeys'; - // @codingStandardsIgnoreEnd - - // @codingStandardsIgnoreEnd - private $timeoutConfigPath = 'currency/yahoofinance/timeout'; + /** + * @var string + */ + const CURRENCY_CONVERTER_URL = 'http://free.currencyconverterapi.com/api/v3/convert?q={{CURRENCY_FROM}}_{{CURRENCY_TO}}&compact=ultra'; //@codingStandardsIgnoreLine /** * Http Client Factory * * @var \Magento\Framework\HTTP\ZendClientFactory */ - protected $httpClientFactory; + private $httpClientFactory; /** * Core scope config @@ -79,28 +74,29 @@ public function fetchRates() */ private function convertBatch($data, $currencyFrom, $currenciesTo) { - $url = $this->buildUrl($currencyFrom, $currenciesTo); - set_time_limit(0); - try { - $response = $this->getServiceResponse($url); - } finally { - ini_restore('max_execution_time'); - } - - foreach ($currenciesTo as $currencyTo) { - if ($currencyFrom == $currencyTo) { - $data[$currencyFrom][$currencyTo] = $this->_numberFormat(1); - } else { - if (empty($response[$currencyFrom . $currencyTo])) { - $this->_messages[] = __('We can\'t retrieve a rate from %1 for %2.', $url, $currencyTo); - $data[$currencyFrom][$currencyTo] = null; + foreach ($currenciesTo as $to) { + set_time_limit(0); + try { + $url = str_replace('{{CURRENCY_FROM}}', $currencyFrom, self::CURRENCY_CONVERTER_URL); + $url = str_replace('{{CURRENCY_TO}}', $to, $url); + $response = $this->getServiceResponse($url); + if ($currencyFrom == $to) { + $data[$currencyFrom][$to] = $this->_numberFormat(1); } else { - $data[$currencyFrom][$currencyTo] = $this->_numberFormat( - (double)$response[$currencyFrom . $currencyTo] - ); + if (empty($response)) { + $this->_messages[] = __('We can\'t retrieve a rate from %1 for %2.', $url, $to); + $data[$currencyFrom][$to] = null; + } else { + $data[$currencyFrom][$to] = $this->_numberFormat( + (double)$response[$currencyFrom . '_' . $to] + ); + } } + } finally { + ini_restore('max_execution_time'); } } + return $data; } @@ -116,13 +112,14 @@ private function getServiceResponse($url, $retry = 0) /** @var \Magento\Framework\HTTP\ZendClient $httpClient */ $httpClient = $this->httpClientFactory->create(); $response = []; + try { $jsonResponse = $httpClient->setUri( $url )->setConfig( [ 'timeout' => $this->scopeConfig->getValue( - $this->timeoutConfigPath, + 'currency/currencyconverterapi/timeout', \Magento\Store\Model\ScopeInterface::SCOPE_STORE ), ] @@ -130,10 +127,7 @@ private function getServiceResponse($url, $retry = 0) 'GET' )->getBody(); - $jsonResponse = json_decode($jsonResponse, true); - if (!empty($jsonResponse['query']['results']['rate'])) { - $response = array_column($jsonResponse['query']['results']['rate'], 'Rate', 'id'); - } + $response = json_decode($jsonResponse, true); } catch (\Exception $e) { if ($retry == 0) { $response = $this->getServiceResponse($url, 1); @@ -147,31 +141,6 @@ private function getServiceResponse($url, $retry = 0) */ protected function _convert($currencyFrom, $currencyTo) { - } - - /** - * Build url for Currency Service - * - * @param string $currencyFrom - * @param string[] $currenciesTo - * @return string - */ - private function buildUrl($currencyFrom, $currenciesTo) - { - $query = urlencode('select ') . '*' . urlencode(' from yahoo.finance.xchange where pair in ('); - $query .= - urlencode( - implode( - ',', - array_map( - function ($currencyTo) use ($currencyFrom) { - return '"' . $currencyFrom . $currencyTo . '"'; - }, - $currenciesTo - ) - ) - ); - $query .= ')'; - return str_replace('{{YQL_STRING}}', $query, $this->currencyConverterUrl); + return 1; } } diff --git a/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php b/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php index 3e0bd5368509e..af49d6daaf379 100644 --- a/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php +++ b/app/code/Magento/Directory/Model/Currency/Import/FixerIo.php @@ -5,15 +5,18 @@ */ namespace Magento\Directory\Model\Currency\Import; +use Magento\Store\Model\ScopeInterface; + /** * Currency rate import model (From http://fixer.io/) */ -class FixerIo extends \Magento\Directory\Model\Currency\Import\AbstractImport +class FixerIo extends AbstractImport { /** * @var string */ - const CURRENCY_CONVERTER_URL = 'http://api.fixer.io/latest?base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}'; + const CURRENCY_CONVERTER_URL = 'http://data.fixer.io/api/latest?access_key={{ACCESS_KEY}}' + . '&base={{CURRENCY_FROM}}&symbols={{CURRENCY_TO}}'; /** * Http Client Factory @@ -47,7 +50,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function fetchRates() { @@ -65,6 +68,13 @@ public function fetchRates() return $data; } + /** + * @inheritdoc + */ + protected function _convert($currencyFrom, $currencyTo) + { + } + /** * Return currencies convert rates in batch mode * @@ -73,11 +83,21 @@ public function fetchRates() * @param array $currenciesTo * @return array */ - private function convertBatch($data, $currencyFrom, $currenciesTo) + private function convertBatch(array $data, string $currencyFrom, array $currenciesTo): array { + $accessKey = $this->scopeConfig->getValue('currency/fixerio/api_key', ScopeInterface::SCOPE_STORE); + if (empty($accessKey)) { + $this->_messages[] = __('No API Key was specified or an invalid API Key was specified.'); + $data[$currencyFrom] = $this->makeEmptyResponse($currenciesTo); + return $data; + } + $currenciesStr = implode(',', $currenciesTo); - $url = str_replace('{{CURRENCY_FROM}}', $currencyFrom, self::CURRENCY_CONVERTER_URL); - $url = str_replace('{{CURRENCY_TO}}', $currenciesStr, $url); + $url = str_replace( + ['{{ACCESS_KEY}}', '{{CURRENCY_FROM}}', '{{CURRENCY_TO}}'], + [$accessKey, $currencyFrom, $currenciesStr], + self::CURRENCY_CONVERTER_URL + ); set_time_limit(0); try { @@ -86,6 +106,11 @@ private function convertBatch($data, $currencyFrom, $currenciesTo) ini_restore('max_execution_time'); } + if (!$this->validateResponse($response, $currencyFrom)) { + $data[$currencyFrom] = $this->makeEmptyResponse($currenciesTo); + return $data; + } + foreach ($currenciesTo as $currencyTo) { if ($currencyFrom == $currencyTo) { $data[$currencyFrom][$currencyTo] = $this->_numberFormat(1); @@ -110,25 +135,24 @@ private function convertBatch($data, $currencyFrom, $currenciesTo) * @param int $retry * @return array */ - private function getServiceResponse($url, $retry = 0) + private function getServiceResponse(string $url, int $retry = 0): array { /** @var \Magento\Framework\HTTP\ZendClient $httpClient */ $httpClient = $this->httpClientFactory->create(); $response = []; try { - $jsonResponse = $httpClient->setUri( - $url - )->setConfig( - [ - 'timeout' => $this->scopeConfig->getValue( - 'currency/fixerio/timeout', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ), - ] - )->request( - 'GET' - )->getBody(); + $jsonResponse = $httpClient->setUri($url) + ->setConfig( + [ + 'timeout' => $this->scopeConfig->getValue( + 'currency/fixerio/timeout', + ScopeInterface::SCOPE_STORE + ), + ] + ) + ->request('GET') + ->getBody(); $response = json_decode($jsonResponse, true); } catch (\Exception $e) { @@ -140,9 +164,38 @@ private function getServiceResponse($url, $retry = 0) } /** - * {@inheritdoc} + * Validates rates response. + * + * @param array $response + * @param string $baseCurrency + * @return bool */ - protected function _convert($currencyFrom, $currencyTo) + private function validateResponse(array $response, string $baseCurrency): bool + { + if ($response['success']) { + return true; + } + + $errorCodes = [ + 101 => __('No API Key was specified or an invalid API Key was specified.'), + 102 => __('The account this API request is coming from is inactive.'), + 105 => __('The "%1" is not allowed as base currency for your subscription plan.', $baseCurrency), + 201 => __('An invalid base currency has been entered.'), + ]; + + $this->_messages[] = $errorCodes[$response['error']['code']] ?? __('Currency rates can\'t be retrieved.'); + + return false; + } + + /** + * Creates array for provided currencies with empty rates. + * + * @param array $currenciesTo + * @return array + */ + private function makeEmptyResponse(array $currenciesTo): array { + return array_fill_keys($currenciesTo, null); } } diff --git a/app/code/Magento/Directory/Model/Currency/Import/Webservicex.php b/app/code/Magento/Directory/Model/Currency/Import/Webservicex.php deleted file mode 100644 index fa3c3a9018dbc..0000000000000 --- a/app/code/Magento/Directory/Model/Currency/Import/Webservicex.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Directory\Model\Currency\Import; - -/** - * Currency rate import model (From www.webservicex.net) - */ -class Webservicex extends \Magento\Directory\Model\Currency\Import\AbstractImport -{ - /** - * Currency converter url - */ - const CURRENCY_CONVERTER_URL = 'http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate' - . '?FromCurrency={{CURRENCY_FROM}}&ToCurrency={{CURRENCY_TO}}'; - - /** - * Http Client Factory - * - * @var \Magento\Framework\HTTP\ZendClientFactory - */ - protected $httpClientFactory; - - /** - * Core scope config - * - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - private $scopeConfig; - - /** - * Constructor - * - * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\HTTP\ZendClientFactory|null $zendClientFactory - */ - public function __construct( - \Magento\Directory\Model\CurrencyFactory $currencyFactory, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\HTTP\ZendClientFactory $zendClientFactory = null - ) { - parent::__construct($currencyFactory); - $this->scopeConfig = $scopeConfig; - $this->httpClientFactory = $zendClientFactory ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\HTTP\ZendClientFactory::class); - } - - /** - * @param string $currencyFrom - * @param string $currencyTo - * @param int $retry - * @return float|null - */ - protected function _convert($currencyFrom, $currencyTo, $retry = 0) - { - $url = str_replace('{{CURRENCY_FROM}}', $currencyFrom, self::CURRENCY_CONVERTER_URL); - $url = str_replace('{{CURRENCY_TO}}', $currencyTo, $url); - /** @var \Magento\Framework\HTTP\ZendClient $httpClient */ - $httpClient = $this->httpClientFactory->create(); - - try { - $response = $httpClient->setUri( - $url - )->setConfig( - [ - 'timeout' => $this->scopeConfig->getValue( - 'currency/webservicex/timeout', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ), - ] - )->request( - 'GET' - )->getBody(); - - $xml = simplexml_load_string($response, null, LIBXML_NOERROR); - if (!$xml || (isset($xml[0]) && $xml[0] == -1)) { - $this->_messages[] = __('We can\'t retrieve a rate from %1.', $url); - return null; - } - return (double)$xml; - } catch (\Exception $e) { - if ($retry == 0) { - $this->_convert($currencyFrom, $currencyTo, 1); - } else { - $this->_messages[] = __('We can\'t retrieve a rate from %1.', $url); - } - } - } -} diff --git a/app/code/Magento/Directory/Test/Mftf/composer.json b/app/code/Magento/Directory/Test/Mftf/composer.json deleted file mode 100644 index c23156f6d923e..0000000000000 --- a/app/code/Magento/Directory/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-directory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php b/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php index 5931e5a839341..c0d549ee11d6d 100644 --- a/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php +++ b/app/code/Magento/Directory/Test/Unit/Model/AllowedCountriesTest.php @@ -8,7 +8,6 @@ use Magento\Directory\Model\AllowedCountries; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Data\Collection\AbstractDb; use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; @@ -30,6 +29,9 @@ class AllowedCountriesTest extends \PHPUnit\Framework\TestCase */ private $allowedCountriesReader; + /** + * Test setUp + */ public function setUp() { $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); @@ -41,6 +43,9 @@ public function setUp() ); } + /** + * Test for getAllowedCountries + */ public function testGetAllowedCountriesWithEmptyFilter() { $website1 = $this->createMock(WebsiteInterface::class); @@ -58,6 +63,9 @@ public function testGetAllowedCountriesWithEmptyFilter() $this->assertEquals(['AM' => 'AM'], $this->allowedCountriesReader->getAllowedCountries()); } + /** + * Test for getAllowedCountries + */ public function testGetAllowedCountries() { $this->scopeConfigMock->expects($this->once()) @@ -70,4 +78,23 @@ public function testGetAllowedCountries() $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_WEBSITE, true) ); } + + /** + * Test for getAllowedCountries + */ + public function testGetAllowedCountriesDefaultScope() + { + $this->storeManagerMock->expects($this->never()) + ->method('getStore'); + + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->with(AllowedCountries::ALLOWED_COUNTRIES_PATH, ScopeInterface::SCOPE_STORE, 0) + ->willReturn('AM'); + + $this->assertEquals( + ['AM' => 'AM'], + $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_STORE, 0) + ); + } } diff --git a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php index c1c3f2fbacb03..7efcf0d62712a 100644 --- a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php +++ b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/FixerIoTest.php @@ -3,89 +3,123 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Directory\Test\Unit\Model\Currency\Import; +use Magento\Directory\Model\Currency; +use Magento\Directory\Model\Currency\Import\FixerIo; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DataObject; +use Magento\Framework\HTTP\ZendClient; +use Magento\Framework\HTTP\ZendClientFactory; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * FixerIo Test + */ class FixerIoTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Directory\Model\Currency\Import\FixerIo + * @var FixerIo */ private $model; /** - * @var \Magento\Directory\Model\CurrencyFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CurrencyFactory|MockObject */ - private $currencyFactoryMock; + private $currencyFactory; /** - * @var \Magento\Framework\HTTP\ZendClientFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ZendClientFactory|MockObject */ - private $httpClientFactoryMock; + private $httpClientFactory; + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfig; + + /** + * @inheritdoc + */ protected function setUp() { - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->currencyFactoryMock = $this->getMockBuilder(\Magento\Directory\Model\CurrencyFactory::class) + $this->currencyFactory = $this->getMockBuilder(CurrencyFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->httpClientFactoryMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClientFactory::class) + $this->httpClientFactory = $this->getMockBuilder(ZendClientFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $scopeMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->model = $objectManagerHelper->getObject( - \Magento\Directory\Model\Currency\Import\FixerIo::class, - [ - 'currencyFactory' => $this->currencyFactoryMock, - 'scopeConfig' => $scopeMock, - 'httpClientFactory' => $this->httpClientFactoryMock - ] - ); + $this->model = new FixerIo($this->currencyFactory, $this->scopeConfig, $this->httpClientFactory); } - public function testFetchRates() + /** + * Test Fetch Rates + * + * @return void + */ + public function testFetchRates(): void { $currencyFromList = ['USD']; $currencyToList = ['EUR', 'UAH']; - $responseBody = '{"base":"USD","date":"2015-10-07","rates":{"EUR":0.9022}}'; + $responseBody = '{"success":"true","base":"USD","date":"2015-10-07","rates":{"EUR":0.9022}}'; $expectedCurrencyRateList = ['USD' => ['EUR' => 0.9022, 'UAH' => null]]; - $message = "We can't retrieve a rate from http://api.fixer.io/latest?base=USD&symbols=EUR,UAH for UAH."; + $message = "We can't retrieve a rate from " + . "http://data.fixer.io/api/latest?access_key=api_key&base=USD&symbols=EUR,UAH for UAH."; + + $this->scopeConfig->method('getValue') + ->withConsecutive( + ['currency/fixerio/api_key', 'store'], + ['currency/fixerio/timeout', 'store'] + ) + ->willReturnOnConsecutiveCalls('api_key', 100); - /** @var \Magento\Directory\Model\Currency|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $currencyMock = $this->getMockBuilder(\Magento\Directory\Model\Currency::class) + /** @var Currency|MockObject $currency */ + $currency = $this->getMockBuilder(Currency::class) ->disableOriginalConstructor() - ->setMethods([]) ->getMock(); - /** @var \Magento\Framework\HTTP\ZendClient|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $httpClientMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClient::class) + /** @var ZendClient|MockObject $httpClient */ + $httpClient = $this->getMockBuilder(ZendClient::class) ->disableOriginalConstructor() - ->setMethods([]) ->getMock(); - /** @var \Zend_Http_Response|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $httpResponseMock = $this->getMockBuilder(\Zend_Http_Response::class) + /** @var DataObject|MockObject $currencyMock */ + $httpResponse = $this->getMockBuilder(DataObject::class) ->disableOriginalConstructor() - ->setMethods([]) + ->setMethods(['getBody']) ->getMock(); - $this->currencyFactoryMock->expects($this->any())->method('create')->willReturn($currencyMock); - $currencyMock->expects($this->once())->method('getConfigBaseCurrencies')->willReturn($currencyFromList); - $currencyMock->expects($this->once())->method('getConfigAllowCurrencies')->willReturn($currencyToList); - $this->httpClientFactoryMock->expects($this->any())->method('create')->willReturn($httpClientMock); - $httpClientMock->expects($this->atLeastOnce())->method('setUri')->willReturnSelf(); - $httpClientMock->expects($this->atLeastOnce())->method('setConfig')->willReturnSelf(); - $httpClientMock->expects($this->atLeastOnce())->method('request')->willReturn($httpResponseMock); - $httpResponseMock->expects($this->any())->method('getBody')->willReturn($responseBody); - $this->assertEquals($expectedCurrencyRateList, $this->model->fetchRates()); + $this->currencyFactory->method('create') + ->willReturn($currency); + $currency->method('getConfigBaseCurrencies') + ->willReturn($currencyFromList); + $currency->method('getConfigAllowCurrencies') + ->willReturn($currencyToList); + + $this->httpClientFactory->method('create') + ->willReturn($httpClient); + $httpClient->method('setUri') + ->willReturnSelf(); + $httpClient->method('setConfig') + ->willReturnSelf(); + $httpClient->method('request') + ->willReturn($httpResponse); + $httpResponse->method('getBody') + ->willReturn($responseBody); + + self::assertEquals($expectedCurrencyRateList, $this->model->fetchRates()); + $messages = $this->model->getMessages(); - $this->assertNotEmpty($messages); - $this->assertTrue(is_array($messages)); - $this->assertEquals($message, (string)$messages[0]); + self::assertNotEmpty($messages); + self::assertTrue(is_array($messages)); + self::assertEquals($message, (string)$messages[0]); } } diff --git a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/YahooFinanceTest.php b/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/YahooFinanceTest.php deleted file mode 100644 index b7b9015ffdfe6..0000000000000 --- a/app/code/Magento/Directory/Test/Unit/Model/Currency/Import/YahooFinanceTest.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Directory\Test\Unit\Model\Currency\Import; - -class YahooFinanceTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Directory\Model\Currency\Import\YahooFinance - */ - private $model; - - /** - * @var \Magento\Directory\Model\CurrencyFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $currencyFactoryMock; - - /** - * @var \Magento\Framework\HTTP\ZendClientFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $httpClientFactoryMock; - - protected function setUp() - { - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->currencyFactoryMock = $this->getMockBuilder(\Magento\Directory\Model\CurrencyFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->httpClientFactoryMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClientFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $scopeMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->model = $objectManagerHelper->getObject( - \Magento\Directory\Model\Currency\Import\YahooFinance::class, - [ - 'currencyFactory' => $this->currencyFactoryMock, - 'scopeConfig' => $scopeMock, - 'httpClientFactory' => $this->httpClientFactoryMock - ] - ); - } - - public function testFetchRates() - { - $currencyFromList = ['USD']; - $currencyToList = ['EUR', 'UAH']; - $responseBody = '{"query":{"count":7,"created":"2016-04-05T16:46:55Z","lang":"en-US","results":{"rate":' - . '[{"id":"USDEUR","Name":"USD/EUR","Rate":"0.9022","Date":"4/5/2016"}]}}}'; - $expectedCurrencyRateList = ['USD' => ['EUR' => 0.9022, 'UAH' => null]]; - $message = "We can't retrieve a rate from http://query.yahooapis.com/v1/public/yql?format=json" - . "&q=select+*+from+yahoo.finance.xchange+where+pair+in+%28%22USDEUR%22%2C%22USDUAH%22)" - . "&env=store://datatables.org/alltableswithkeys for UAH."; - - /** @var \Magento\Directory\Model\Currency|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $currencyMock = $this->getMockBuilder(\Magento\Directory\Model\Currency::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - /** @var \Magento\Framework\HTTP\ZendClient|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $httpClientMock = $this->getMockBuilder(\Magento\Framework\HTTP\ZendClient::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - /** @var \Zend_Http_Response|\PHPUnit_Framework_MockObject_MockObject $currencyMock */ - $httpResponseMock = $this->getMockBuilder('Zend_Http_Response') - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->currencyFactoryMock->expects($this->any())->method('create')->willReturn($currencyMock); - $currencyMock->expects($this->once())->method('getConfigBaseCurrencies')->willReturn($currencyFromList); - $currencyMock->expects($this->once())->method('getConfigAllowCurrencies')->willReturn($currencyToList); - $this->httpClientFactoryMock->expects($this->any())->method('create')->willReturn($httpClientMock); - $httpClientMock->expects($this->atLeastOnce())->method('setUri')->willReturnSelf(); - $httpClientMock->expects($this->atLeastOnce())->method('setConfig')->willReturnSelf(); - $httpClientMock->expects($this->atLeastOnce())->method('request')->willReturn($httpResponseMock); - $httpResponseMock->expects($this->any())->method('getBody')->willReturn($responseBody); - - $this->assertEquals($expectedCurrencyRateList, $this->model->fetchRates()); - $messages = $this->model->getMessages(); - $this->assertNotEmpty($messages); - $this->assertTrue(is_array($messages)); - $this->assertEquals($message, (string)$messages[0]); - } -} diff --git a/app/code/Magento/Directory/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Directory/Test/Unit/Model/ObserverTest.php deleted file mode 100644 index 5fb7271a411ed..0000000000000 --- a/app/code/Magento/Directory/Test/Unit/Model/ObserverTest.php +++ /dev/null @@ -1,153 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Directory\Test\Unit\Model; - -use Magento\Store\Model\ScopeInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Directory\Model\Observer; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ObserverTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ - protected $objectManager; - - /** @var Observer */ - protected $observer; - - /** @var \Magento\Directory\Model\Currency\Import\Factory|\PHPUnit_Framework_MockObject_MockObject */ - protected $importFactory; - - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfig; - - /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilder; - - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; - - /** @var \Magento\Directory\Model\CurrencyFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $currencyFactory; - - /** @var \Magento\Framework\Translate\Inline\StateInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $inlineTranslation; - - protected function setUp() - { - $this->objectManager = new ObjectManager($this); - - $this->importFactory = $this->getMockBuilder(\Magento\Directory\Model\Currency\Import\Factory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\MutableScopeConfig::class) - ->disableOriginalConstructor() - ->setMethods(['getValue']) - ->getMock(); - $this->transportBuilder = $this->getMockBuilder(\Magento\Framework\Mail\Template\TransportBuilder::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->currencyFactory = $this->getMockBuilder(\Magento\Directory\Model\CurrencyFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->inlineTranslation = $this->getMockBuilder(\Magento\Framework\Translate\Inline\StateInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->observer = $this->objectManager->getObject( - \Magento\Directory\Model\Observer::class, - [ - 'importFactory' => $this->importFactory, - 'scopeConfig' => $this->scopeConfig, - 'transportBuilder' => $this->transportBuilder, - 'storeManager' => $this->storeManager, - 'currencyFactory' => $this->currencyFactory, - 'inlineTranslation' => $this->inlineTranslation - ] - ); - } - - public function testScheduledUpdateCurrencyRates() - { - $this->scopeConfig - ->expects($this->at(0)) - ->method('getValue') - ->with(Observer::IMPORT_ENABLE, ScopeInterface::SCOPE_STORE) - ->will($this->returnValue(1)); - $this->scopeConfig - ->expects($this->at(1)) - ->method('getValue') - ->with(Observer::CRON_STRING_PATH, ScopeInterface::SCOPE_STORE) - ->will($this->returnValue('cron-path')); - $this->scopeConfig - ->expects($this->at(2)) - ->method('getValue') - ->with(Observer::IMPORT_SERVICE, ScopeInterface::SCOPE_STORE) - ->will($this->returnValue('import-service')); - $importInterfaceMock = $this->getMockBuilder(\Magento\Directory\Model\Currency\Import\Webservicex::class) - ->disableOriginalConstructor() - ->setMethods(['fetchRates', 'getMessages']) - ->getMock(); - $importInterfaceMock->expects($this->once()) - ->method('fetchRates') - ->will($this->returnValue([])); - $importInterfaceMock->expects($this->once()) - ->method('getMessages') - ->will($this->returnValue([])); - - $this->importFactory - ->expects($this->once()) - ->method('create') - ->with('import-service') - ->will($this->returnValue($importInterfaceMock)); - - $currencyMock = $this->getMockBuilder(\Magento\Directory\Model\Currency::class) - ->disableOriginalConstructor() - ->setMethods(['saveRates', '__wakeup', '__sleep']) - ->getMock(); - $currencyMock->expects($this->once()) - ->method('saveRates') - ->will($this->returnValue(null)); - $this->currencyFactory - ->expects($this->once()) - ->method('create') - ->will($this->returnValue($currencyMock)); - - $this->observer->scheduledUpdateCurrencyRates(null); - } - - /** - * @expectedException \Exception - */ - public function testScheduledUpdateCurrencyRatesThrowsException() - { - $this->scopeConfig->expects($this->exactly(3)) - ->method('getValue') - ->willReturnMap( - [ - [Observer::IMPORT_ENABLE, ScopeInterface::SCOPE_STORE, null, 1], - [Observer::CRON_STRING_PATH, ScopeInterface::SCOPE_STORE, null, 'cron-path'], - [Observer::IMPORT_SERVICE, ScopeInterface::SCOPE_STORE, null, 'import-service'] - ] - ); - - $this->importFactory - ->expects($this->once()) - ->method('create') - ->with('import-service') - ->willThrowException(new \Exception()); - - $this->observer->scheduledUpdateCurrencyRates(null); - } -} diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 15a82e006bffe..ec5fa35b6a152 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -34,21 +34,20 @@ <can_be_empty>1</can_be_empty> </field> </group> - <group id="yahoofinance" translate="label" sortOrder="33" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Yahoo Finance Exchange</label> - <field id="timeout" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Connection Timeout in Seconds</label> - </field> - </group> <group id="fixerio" translate="label" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Fixer.io</label> - <field id="timeout" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="api_key" translate="label" type="obscure" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>API Key</label> + <config_path>currency/fixerio/api_key</config_path> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + </field> + <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Connection Timeout in Seconds</label> </field> </group> - <group id="webservicex" translate="label" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Webservicex</label> - <field id="timeout" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <group id="currencyconverterapi" translate="label" sortOrder="45" showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Currency Converter API</label> + <field id="timeout" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Connection Timeout in Seconds</label> </field> </group> diff --git a/app/code/Magento/Directory/etc/config.xml b/app/code/Magento/Directory/etc/config.xml index fa4e9d64d10d8..276d7088cc2ea 100644 --- a/app/code/Magento/Directory/etc/config.xml +++ b/app/code/Magento/Directory/etc/config.xml @@ -18,15 +18,13 @@ <base>USD</base> <default>USD</default> </options> - <yahoofinance> - <timeout>100</timeout> - </yahoofinance> <fixerio> <timeout>100</timeout> + <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> </fixerio> - <webservicex> + <currencyconverterapi> <timeout>100</timeout> - </webservicex> + </currencyconverterapi> <import> <enabled>0</enabled> <error_email /> diff --git a/app/code/Magento/Directory/etc/db_schema_whitelist.json b/app/code/Magento/Directory/etc/db_schema_whitelist.json index a71261851e361..ab2facb17428a 100644 --- a/app/code/Magento/Directory/etc/db_schema_whitelist.json +++ b/app/code/Magento/Directory/etc/db_schema_whitelist.json @@ -1,65 +1,65 @@ { - "directory_country": { - "column": { - "country_id": true, - "iso2_code": true, - "iso3_code": true + "directory_country": { + "column": { + "country_id": true, + "iso2_code": true, + "iso3_code": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "directory_country_format": { - "column": { - "country_format_id": true, - "country_id": true, - "type": true, - "format": true - }, - "constraint": { - "PRIMARY": true, - "DIRECTORY_COUNTRY_FORMAT_COUNTRY_ID_TYPE": true - } - }, - "directory_country_region": { - "column": { - "region_id": true, - "country_id": true, - "code": true, - "default_name": true - }, - "index": { - "DIRECTORY_COUNTRY_REGION_COUNTRY_ID": true + "directory_country_format": { + "column": { + "country_format_id": true, + "country_id": true, + "type": true, + "format": true + }, + "constraint": { + "PRIMARY": true, + "DIRECTORY_COUNTRY_FORMAT_COUNTRY_ID_TYPE": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "directory_country_region_name": { - "column": { - "locale": true, - "region_id": true, - "name": true - }, - "index": { - "DIRECTORY_COUNTRY_REGION_NAME_REGION_ID": true - }, - "constraint": { - "PRIMARY": true, - "DIR_COUNTRY_REGION_NAME_REGION_ID_DIR_COUNTRY_REGION_REGION_ID": true - } - }, - "directory_currency_rate": { - "column": { - "currency_from": true, - "currency_to": true, - "rate": true + "directory_country_region": { + "column": { + "region_id": true, + "country_id": true, + "code": true, + "default_name": true + }, + "index": { + "DIRECTORY_COUNTRY_REGION_COUNTRY_ID": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "DIRECTORY_CURRENCY_RATE_CURRENCY_TO": true + "directory_country_region_name": { + "column": { + "locale": true, + "region_id": true, + "name": true + }, + "index": { + "DIRECTORY_COUNTRY_REGION_NAME_REGION_ID": true + }, + "constraint": { + "PRIMARY": true, + "DIR_COUNTRY_REGION_NAME_REGION_ID_DIR_COUNTRY_REGION_REGION_ID": true + } }, - "constraint": { - "PRIMARY": true + "directory_currency_rate": { + "column": { + "currency_from": true, + "currency_to": true, + "rate": true + }, + "index": { + "DIRECTORY_CURRENCY_RATE_CURRENCY_TO": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Directory/etc/di.xml b/app/code/Magento/Directory/etc/di.xml index 02e16af29ea14..50cd65cc5045c 100644 --- a/app/code/Magento/Directory/etc/di.xml +++ b/app/code/Magento/Directory/etc/di.xml @@ -10,18 +10,14 @@ <type name="Magento\Directory\Model\Currency\Import\Config"> <arguments> <argument name="servicesConfig" xsi:type="array"> - <item name="yahoofinance" xsi:type="array"> - <item name="label" xsi:type="string" translatable="true">Yahoo Finance Exchange</item> - <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\YahooFinance</item> - </item> - <item name="webservicex" xsi:type="array"> - <item name="label" xsi:type="string" translatable="true">Webservicex</item> - <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\Webservicex</item> - </item> <item name="fixerio" xsi:type="array"> <item name="label" xsi:type="string" translatable="true">Fixer.io</item> <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\FixerIo</item> </item> + <item name="currencyconverterapi" xsi:type="array"> + <item name="label" xsi:type="string" translatable="true">Currency Converter API</item> + <item name="class" xsi:type="string">Magento\Directory\Model\Currency\Import\CurrencyConverterApi</item> + </item> </argument> </arguments> </type> diff --git a/app/code/Magento/Directory/etc/zip_codes.xml b/app/code/Magento/Directory/etc/zip_codes.xml index d9041d1ff50a7..3c540f7ce0ffd 100644 --- a/app/code/Magento/Directory/etc/zip_codes.xml +++ b/app/code/Magento/Directory/etc/zip_codes.xml @@ -196,7 +196,7 @@ </zip> <zip countryCode="IL"> <codes> - <code id="pattern_1" active="true" example="12345">^[0-9]{5}$</code> + <code id="pattern_1" active="true" example="6687865">^[0-9]{7}$</code> </codes> </zip> <zip countryCode="IT"> diff --git a/app/code/Magento/Directory/i18n/en_US.csv b/app/code/Magento/Directory/i18n/en_US.csv index 00e32ab8c8501..3dcd2ceebf134 100644 --- a/app/code/Magento/Directory/i18n/en_US.csv +++ b/app/code/Magento/Directory/i18n/en_US.csv @@ -30,10 +30,8 @@ Continue,Continue " "Default Display Currency","Default Display Currency" "Allowed Currencies","Allowed Currencies" -"Yahoo Finance Exchange","Yahoo Finance Exchange" "Connection Timeout in Seconds","Connection Timeout in Seconds" Fixer.io,Fixer.io -Webservicex,Webservicex "Scheduled Import Settings","Scheduled Import Settings" Enabled,Enabled "Error Email Recipient","Error Email Recipient" @@ -49,3 +47,8 @@ Service,Service "State is Required for","State is Required for" "Allow to Choose State if It is Optional for Country","Allow to Choose State if It is Optional for Country" "Weight Unit","Weight Unit" +"No API Key was specified or an invalid API Key was specified.","No API Key was specified or an invalid API Key was specified." +"The account this API request is coming from is inactive.","The account this API request is coming from is inactive." +"The """%1"" is not allowed as base currency for your subscription plan.","The """%1"" is not allowed as base currency for your subscription plan." +"An invalid base currency has been entered.","An invalid base currency has been entered." +"Currency rates can't be retrieved.","Currency rates can't be retrieved." diff --git a/app/code/Magento/Downloadable/Block/Catalog/Product/Samples.php b/app/code/Magento/Downloadable/Block/Catalog/Product/Samples.php index 120c7a38a8f41..859d6a53d0618 100644 --- a/app/code/Magento/Downloadable/Block/Catalog/Product/Samples.php +++ b/app/code/Magento/Downloadable/Block/Catalog/Product/Samples.php @@ -6,7 +6,8 @@ namespace Magento\Downloadable\Block\Catalog\Product; -use Magento\Downloadable\Model\ResourceModel\Sample; +use Magento\Downloadable\Model\ResourceModel\Sample\Collection as SampleCollection; +use Magento\Downloadable\Api\Data\SampleInterface; /** * Downloadable Product Samples part block @@ -29,7 +30,7 @@ public function hasSamples() /** * Get downloadable product samples * - * @return array + * @return SampleCollection */ public function getSamples() { @@ -37,7 +38,7 @@ public function getSamples() } /** - * @param Sample $sample + * @param SampleInterface $sample * @return string */ public function getSampleUrl($sample) diff --git a/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php b/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php index 855fac5041b21..90b458ff6348e 100644 --- a/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php +++ b/app/code/Magento/Downloadable/Model/ResourceModel/Indexer/Price.php @@ -6,73 +6,177 @@ namespace Magento\Downloadable\Model\ResourceModel\Indexer; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BasePriceModifier; +use Magento\Downloadable\Model\Product\Type; +use Magento\Eav\Model\Config; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Query\BaseFinalPrice; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; /** - * Downloadable products Price indexer resource model - * - * @author Magento Core Team <core@magentocommerce.com> + * Downloadable Product Price Indexer Resource model + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Price extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice +class Price implements DimensionalIndexerInterface { /** - * @param null|int|array $entityIds - * @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice + * @var BaseFinalPrice */ - protected function reindex($entityIds = null) - { - if ($this->hasEntity() || !empty($entityIds)) { - $this->_prepareFinalPriceData($entityIds); - $this->_applyCustomOption(); - $this->_applyDownloadableLink(); - $this->_movePriceDataToIndexTable(); - } + private $baseFinalPrice; - return $this; + /** + * @var IndexTableStructureFactory + */ + private $indexTableStructureFactory; + + /** + * @var TableMaintainer + */ + private $tableMaintainer; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var string + */ + private $connectionName; + + /** + * @var \Magento\Framework\DB\Adapter\AdapterInterface + */ + private $connection; + + /** + * @var Config + */ + private $eavConfig; + + /** + * @var BasePriceModifier + */ + private $basePriceModifier; + + /** + * @param BaseFinalPrice $baseFinalPrice + * @param IndexTableStructureFactory $indexTableStructureFactory + * @param TableMaintainer $tableMaintainer + * @param MetadataPool $metadataPool + * @param Config $eavConfig + * @param ResourceConnection $resource + * @param BasePriceModifier $basePriceModifier + * @param string $connectionName + */ + public function __construct( + BaseFinalPrice $baseFinalPrice, + IndexTableStructureFactory $indexTableStructureFactory, + TableMaintainer $tableMaintainer, + MetadataPool $metadataPool, + Config $eavConfig, + ResourceConnection $resource, + BasePriceModifier $basePriceModifier, + $connectionName = 'indexer' + ) { + $this->baseFinalPrice = $baseFinalPrice; + $this->indexTableStructureFactory = $indexTableStructureFactory; + $this->tableMaintainer = $tableMaintainer; + $this->connectionName = $connectionName; + $this->metadataPool = $metadataPool; + $this->resource = $resource; + $this->eavConfig = $eavConfig; + $this->basePriceModifier = $basePriceModifier; } /** - * Retrieve downloadable links price temporary index table name - * - * @see _prepareDefaultFinalPriceTable() - * - * @return string + * {@inheritdoc} + * @param array $dimensions + * @param \Traversable $entityIds + * @throws \Exception */ - protected function _getDownloadableLinkPriceTable() + public function executeByDimensions(array $dimensions, \Traversable $entityIds) { - return $this->tableStrategy->getTableName('catalog_product_index_price_downlod'); + $temporaryPriceTable = $this->indexTableStructureFactory->create([ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ]); + $this->fillFinalPrice($dimensions, $entityIds, $temporaryPriceTable); + $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds)); + $this->applyDownloadableLink($temporaryPriceTable, $dimensions); } /** - * Prepare downloadable links price temporary index table + * Calculate and apply Downloadable links price to index * + * @param IndexTableStructure $temporaryPriceTable + * @param array $dimensions * @return $this + * @throws \Exception */ - protected function _prepareDownloadableLinkPriceTable() - { - $this->getConnection()->delete($this->_getDownloadableLinkPriceTable()); + private function applyDownloadableLink( + IndexTableStructure $temporaryPriceTable, + array $dimensions + ) { + $temporaryDownloadableTableName = 'catalog_product_index_price_downlod_temp'; + $this->getConnection()->createTemporaryTableLike( + $temporaryDownloadableTableName, + $this->getTable('catalog_product_index_price_downlod_tmp'), + true + ); + $this->fillTemporaryTable($temporaryDownloadableTableName, $dimensions); + $this->updateTemporaryDownloadableTable($temporaryPriceTable->getTableName(), $temporaryDownloadableTableName); + $this->getConnection()->delete($temporaryDownloadableTableName); return $this; } /** - * Calculate and apply Downloadable links price to index + * Retrieve catalog_product attribute instance by attribute code * - * @return $this + * @param string $attributeCode + * @return \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _applyDownloadableLink() + protected function getAttribute($attributeCode) { - $connection = $this->getConnection(); - $table = $this->_getDownloadableLinkPriceTable(); - $finalPriceTable = $this->_getDefaultFinalPriceTable(); - - $this->_prepareDownloadableLinkPriceTable(); - - $dlType = $this->_getAttribute('links_purchased_separately'); - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); + return $this->eavConfig->getAttribute(Product::ENTITY, $attributeCode); + } - $ifPrice = $connection->getIfNullSql('dlpw.price_id', 'dlpd.price'); + /** + * Put data into catalog product price indexer Downloadable links price temp table + * + * @param string $temporaryDownloadableTableName + * @param array $dimensions + * @return void + * @throws \Exception + */ + private function fillTemporaryTable(string $temporaryDownloadableTableName, array $dimensions) + { + $dlType = $this->getAttribute('links_purchased_separately'); + $ifPrice = $this->getConnection()->getIfNullSql('dlpw.price_id', 'dlpd.price'); + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); - $select = $connection->select()->from( - ['i' => $finalPriceTable], + $select = $this->getConnection()->select()->from( + ['i' => $this->tableMaintainer->getMainTmpTable($dimensions)], ['entity_id', 'customer_group_id', 'website_id'] )->join( ['dl' => $dlType->getBackend()->getTable()], @@ -101,30 +205,87 @@ protected function _applyDownloadableLink() 'max_price' => new \Zend_Db_Expr('SUM(' . $ifPrice . ')'), ] ); + $query = $select->insertFromSelect($temporaryDownloadableTableName); + $this->getConnection()->query($query); + } - $query = $select->insertFromSelect($table); - $connection->query($query); - - $ifTierPrice = $connection->getCheckSql('i.tier_price IS NOT NULL', '(i.tier_price + id.min_price)', 'NULL'); + /** + * Update data in the catalog product price indexer temp table + * + * @param string $temporaryPriceTableName + * @param string $temporaryDownloadableTableName + * @return void + */ + private function updateTemporaryDownloadableTable( + string $temporaryPriceTableName, + string $temporaryDownloadableTableName + ) { + $ifTierPrice = $this->getConnection()->getCheckSql( + 'i.tier_price IS NOT NULL', + '(i.tier_price + id.min_price)', + 'NULL' + ); - $select = $connection->select()->join( - ['id' => $table], + $selectForCrossUpdate = $this->getConnection()->select()->join( + ['id' => $temporaryDownloadableTableName], 'i.entity_id = id.entity_id AND i.customer_group_id = id.customer_group_id' . ' AND i.website_id = id.website_id', [] - )->columns( + ); + // adds price of custom option, that was applied in DefaultPrice::_applyCustomOption + $selectForCrossUpdate->columns( [ 'min_price' => new \Zend_Db_Expr('i.min_price + id.min_price'), 'max_price' => new \Zend_Db_Expr('i.max_price + id.max_price'), 'tier_price' => new \Zend_Db_Expr($ifTierPrice), ] ); + $query = $selectForCrossUpdate->crossUpdateFromSelect(['i' => $temporaryPriceTableName]); + $this->getConnection()->query($query); + } - $query = $select->crossUpdateFromSelect(['i' => $finalPriceTable]); - $connection->query($query); + /** + * Fill final price + * + * @param array $dimensions + * @param \Traversable $entityIds + * @param IndexTableStructure $temporaryPriceTable + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Select_Exception + */ + private function fillFinalPrice( + array $dimensions, + \Traversable $entityIds, + IndexTableStructure $temporaryPriceTable + ) { + $select = $this->baseFinalPrice->getQuery($dimensions, Type::TYPE_DOWNLOADABLE, iterator_to_array($entityIds)); + $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false); + $this->tableMaintainer->getConnection()->query($query); + } - $connection->delete($table); + /** + * Get connection + * + * return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \DomainException + */ + private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface + { + if ($this->connection === null) { + $this->connection = $this->resource->getConnection($this->connectionName); + } - return $this; + return $this->connection; + } + + /** + * Get table + * + * @param string $tableName + * @return string + */ + private function getTable($tableName) + { + return $this->resource->getTableName($tableName, $this->connectionName); } } diff --git a/app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php b/app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php index 4590d5433ed72..73ae696a85187 100644 --- a/app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php +++ b/app/code/Magento/Downloadable/Model/ResourceModel/Link/Collection.php @@ -87,7 +87,7 @@ public function addProductToFilter($product) } /** - * Retrieve title for for current store + * Retrieve title for current store * * @param int $storeId * @return $this @@ -113,7 +113,7 @@ public function addTitleToResult($storeId = 0) } /** - * Retrieve price for for current website + * Retrieve price for current website * * @param int $websiteId * @return $this diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminDownloadableProductActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminDownloadableProductActionGroup.xml index 2e1392eb0d2a8..363911daa41ed 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminDownloadableProductActionGroup.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/AdminDownloadableProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Fill main fields in product form--> <actionGroup name="fillMainDownloadableProductForm"> <arguments> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml index 16f32942a375f..38ac2c99e4756 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="downloadableData" type="downloadable_data"> <data key="link_title">Downloadable Links</data> <data key="sample_title">Downloadable Samples</data> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml index 2c25c2c9b822b..6a91b60dcb588 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="DownloadableProduct" type="product"> <data key="sku" unique="suffix">downloadableproduct</data> <data key="type_id">downloadable</data> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml index 1138b56189137..2511244d445c1 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateDownloadableLink" dataType="downloadable_link" type="create" auth="adminOauth" url="/V1/products/{sku}/downloadable-links" method="POST"> <contentType>application/json</contentType> <object dataType="downloadable_link" key="link"> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml index de899a9051022..d5d6c16c71736 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateLinkFileContent" dataType="link_file_content" type="create"> <field key="file_data">string</field> <field key="name">string</field> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml index 5109263cfc242..3da91807ceb48 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateSampleFileContent" dataType="sample_file_content" type="create"> <field key="file_data">string</field> <field key="name">string</field> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Downloadable/Test/Mftf/Page/AdminProductCreatePage.xml index 7d5cc562bfb3c..049d6d084530f 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Page/AdminProductCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> <section name="AdminProductDownloadableSection"/> </page> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml index 1c1ce0343c94d..274f26498958b 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDownloadableSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductDownloadableSection"> <element name="sectionHeader" type="button" selector="div[data-index='downloadable']" timeout="30" /> <element name="isDownloadableProduct" type="input" selector="input[name='is_downloadable']" /> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml index f2c7f6a61388f..3d779740849c5 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultImageDownloadableProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultImageDownloadableProductTest"> <annotations> <features value="Downloadable"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml new file mode 100644 index 0000000000000..88dcca0958719 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoDownloadableProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add default video for a Downloadable Product"/> + <description value="Admin should be able to add default video for a Downloadable Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-114"/> + <group value="Downloadable"/> + </annotations> + + <!-- Create a downloadable product --> + <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable links --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionHeader}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductDownloadableSection.sectionHeader}}" dependentSelector="{{AdminProductDownloadableSection.isDownloadableProduct}}" visible="false" stepKey="openDownloadableSection" after="scrollToSection"/> + <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <fillField userInput="{{downloadableData.link_title}}" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillLinkTitle" after="checkOptionIsDownloadable"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately" after="fillLinkTitle"/> + <fillField userInput="{{downloadableData.sample_title}}" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillSampleTitle" after="checkOptionPurchaseSeparately"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLinkWithMaxDownloads" after="fillSampleTitle"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <actionGroup ref="addDownloadableProductLink" stepKey="addDownloadableProductLink" before="saveProductForm"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableProductSetEditContentTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableProductSetEditContentTest.xml new file mode 100644 index 0000000000000..72cdf589bec91 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableProductSetEditContentTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDownloadableProductSetEditContentTest" extends="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Create/edit downloadable product"/> + <title value="Admin should be able to set/edit product Content when editing a downloadable product"/> + <description value="Admin should be able to set/edit product Content when editing a downloadable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3426"/> + <group value="Downloadable"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <after> + <!-- Delete downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!--Checking content storefront--> + <amOnPage url="{{DownloadableProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableSetEditRelatedProductsTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableSetEditRelatedProductsTest.xml new file mode 100644 index 0000000000000..f70769cdfe834 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDownloadableSetEditRelatedProductsTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDownloadableSetEditRelatedProductsTest" extends="AdminSimpleSetEditRelatedProductsTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Create/edit downloadable product"/> + <title value="Admin should be able to set/edit Related Products information when editing a downloadable product"/> + <description value="Admin should be able to set/edit Related Products information when editing a downloadable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3416"/> + <group value="Downloadable"/> + </annotations> + <before></before> + <after> + <!-- Delete downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!--See related product in storefront--> + <amOnPage url="{{DownloadableProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml index 370148c6a4167..94809563de3ca 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultImageDownloadableProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageDownloadableProductTest"> <annotations> <features value="Downloadable"/> @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-201"/> <group value="Downloadable"/> + <skip> + <issueId value="MAGETWO-94795"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -59,9 +62,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml new file mode 100644 index 0000000000000..83d859ee7421a --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoDownloadableProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to remove default video from a Downloadable Product"/> + <description value="Admin should be able to remove default video from a Downloadable Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-207"/> + <group value="Downloadable"/> + <skip> + <issueId value="MAGETWO-94795"/> + </skip> + </annotations> + + <!-- Create a downloadable product --> + <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillMainProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable links --> + <scrollTo selector="{{AdminProductDownloadableSection.sectionHeader}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductDownloadableSection.sectionHeader}}" dependentSelector="{{AdminProductDownloadableSection.isDownloadableProduct}}" visible="false" stepKey="openDownloadableSection" after="scrollToSection"/> + <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <fillField userInput="{{downloadableData.link_title}}" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillLinkTitle" after="checkOptionIsDownloadable"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately" after="fillLinkTitle"/> + <fillField userInput="{{downloadableData.sample_title}}" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillSampleTitle" after="checkOptionPurchaseSeparately"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLinkWithMaxDownloads" after="fillSampleTitle"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <actionGroup ref="addDownloadableProductLink" stepKey="addDownloadableProductLink" before="saveProductForm"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml new file mode 100644 index 0000000000000..af5d20b075d12 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchDownloadableByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Downloadable product with product name"/> + <description value="Guest customer should be able to advance search Downloadable product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-142"/> + <group value="Downloadable"/> + </annotations> + <before> + <createData entity="ApiDownloadableProduct" stepKey="product"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="product"/> + </createData> + </before> + </test> + <test name="AdvanceCatalogSearchDownloadableBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Downloadable product with product sku"/> + <description value="Guest customer should be able to advance search Downloadable product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-252"/> + <group value="Downloadable"/> + </annotations> + <before> + <createData entity="ApiDownloadableProduct" stepKey="product"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="product"/> + </createData> + </before> + </test> + <test name="AdvanceCatalogSearchDownloadableByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Downloadable product with product description"/> + <description value="Guest customer should be able to advance search Downloadable product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-243"/> + <group value="Downloadable"/> + </annotations> + <before> + <createData entity="ApiDownloadableProduct" stepKey="product"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="product"/> + </createData> + </before> + </test> + <test name="AdvanceCatalogSearchDownloadableByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Downloadable product with product short description"/> + <description value="Guest customer should be able to advance search Downloadable product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-245"/> + <group value="Downloadable"/> + </annotations> + <before> + <createData entity="ApiDownloadableProduct" stepKey="product"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="product"/> + </createData> + </before> + </test> + <test name="AdvanceCatalogSearchDownloadableByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Downloadable product with product price"/> + <description value="Guest customer should be able to advance search Downloadable product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-246"/> + <group value="Downloadable"/> + </annotations> + <before> + <createData entity="ApiDownloadableProduct" stepKey="product"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="product"/> + </createData> + </before> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml index 4a4242811a39c..75a66cec91692 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <!--Create Downloadable Product--> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitProductPageDownloadable" after="seeSimpleProductInGrid"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/NewProductsListWidgetDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/NewProductsListWidgetDownloadableProductTest.xml new file mode 100644 index 0000000000000..1a9ad271d62c7 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/NewProductsListWidgetDownloadableProductTest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetDownloadableProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="Downloadable"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Downloadable Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Downloadable Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-124"/> + <group value="Downloadable"/> + <group value="WYSIWYGDisabled"/> + <skip> + <issueId value="MQE-1187"/> + </skip> + </annotations> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Create a Downloadable product to appear in the widget --> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProduct"/> + <click selector="{{AdminProductGridActionSection.addDownloadableProduct}}" stepKey="clickAddDownloadableProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{_defaultProduct.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="100" stepKey="fillProductQuantity"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> + <checkOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkIsDownloadable"/> + <fillField userInput="This Is A Title" selector="{{AdminProductDownloadableSection.linksTitleInput}}" stepKey="fillDownloadableLinkTitle"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkLinksPurchasedSeparately"/> + <fillField userInput="This Is Another Title" selector="{{AdminProductDownloadableSection.samplesTitleInput}}" stepKey="fillDownloadableSampleTitle"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableLinkWithMaxDownloads"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here, via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{_defaultProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Downloadable/Test/Mftf/composer.json b/app/code/Magento/Downloadable/Test/Mftf/composer.json deleted file mode 100644 index 6a2a5bb00f01e..0000000000000 --- a/app/code/Magento/Downloadable/Test/Mftf/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/functional-test-module-downloadable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-downloadable-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Downloadable/etc/db_schema_whitelist.json b/app/code/Magento/Downloadable/etc/db_schema_whitelist.json index 29cc8830746d7..61b7d439a4cd4 100644 --- a/app/code/Magento/Downloadable/etc/db_schema_whitelist.json +++ b/app/code/Magento/Downloadable/etc/db_schema_whitelist.json @@ -1,170 +1,170 @@ { - "downloadable_link": { - "column": { - "link_id": true, - "product_id": true, - "sort_order": true, - "number_of_downloads": true, - "is_shareable": true, - "link_url": true, - "link_file": true, - "link_type": true, - "sample_url": true, - "sample_file": true, - "sample_type": true - }, - "index": { - "DOWNLOADABLE_LINK_PRODUCT_ID_SORT_ORDER": true - }, - "constraint": { - "PRIMARY": true, - "DOWNLOADABLE_LINK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true - } - }, - "downloadable_link_price": { - "column": { - "price_id": true, - "link_id": true, - "website_id": true, - "price": true - }, - "index": { - "DOWNLOADABLE_LINK_PRICE_LINK_ID": true, - "DOWNLOADABLE_LINK_PRICE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "DOWNLOADABLE_LINK_PRICE_LINK_ID_DOWNLOADABLE_LINK_LINK_ID": true, - "DOWNLOADABLE_LINK_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "downloadable_link_purchased": { - "column": { - "purchased_id": true, - "order_id": true, - "order_increment_id": true, - "order_item_id": true, - "created_at": true, - "updated_at": true, - "customer_id": true, - "product_name": true, - "product_sku": true, - "link_section_title": true - }, - "index": { - "DOWNLOADABLE_LINK_PURCHASED_ORDER_ID": true, - "DOWNLOADABLE_LINK_PURCHASED_ORDER_ITEM_ID": true, - "DOWNLOADABLE_LINK_PURCHASED_CUSTOMER_ID": true - }, - "constraint": { - "PRIMARY": true, - "DL_LNK_PURCHASED_CSTR_ID_CSTR_ENTT_ENTT_ID": true, - "DOWNLOADABLE_LINK_PURCHASED_ORDER_ID_SALES_ORDER_ENTITY_ID": true - } - }, - "downloadable_link_purchased_item": { - "column": { - "item_id": true, - "purchased_id": true, - "order_item_id": true, - "product_id": true, - "link_hash": true, - "number_of_downloads_bought": true, - "number_of_downloads_used": true, - "link_id": true, - "link_title": true, - "is_shareable": true, - "link_url": true, - "link_file": true, - "link_type": true, - "status": true, - "created_at": true, - "updated_at": true - }, - "index": { - "DOWNLOADABLE_LINK_PURCHASED_ITEM_LINK_HASH": true, - "DOWNLOADABLE_LINK_PURCHASED_ITEM_ORDER_ITEM_ID": true, - "DOWNLOADABLE_LINK_PURCHASED_ITEM_PURCHASED_ID": true - }, - "constraint": { - "PRIMARY": true, - "DL_LNK_PURCHASED_ITEM_PURCHASED_ID_DL_LNK_PURCHASED_PURCHASED_ID": true, - "DL_LNK_PURCHASED_ITEM_ORDER_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true - } - }, - "downloadable_link_title": { - "column": { - "title_id": true, - "link_id": true, - "store_id": true, - "title": true - }, - "index": { - "DOWNLOADABLE_LINK_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "DOWNLOADABLE_LINK_TITLE_LINK_ID_DOWNLOADABLE_LINK_LINK_ID": true, - "DOWNLOADABLE_LINK_TITLE_STORE_ID_STORE_STORE_ID": true, - "DOWNLOADABLE_LINK_TITLE_LINK_ID_STORE_ID": true - } - }, - "downloadable_sample": { - "column": { - "sample_id": true, - "product_id": true, - "sample_url": true, - "sample_file": true, - "sample_type": true, - "sort_order": true - }, - "index": { - "DOWNLOADABLE_SAMPLE_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "DOWNLOADABLE_SAMPLE_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true - } - }, - "downloadable_sample_title": { - "column": { - "title_id": true, - "sample_id": true, - "store_id": true, - "title": true - }, - "index": { - "DOWNLOADABLE_SAMPLE_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "DL_SAMPLE_TTL_SAMPLE_ID_DL_SAMPLE_SAMPLE_ID": true, - "DOWNLOADABLE_SAMPLE_TITLE_STORE_ID_STORE_STORE_ID": true, - "DOWNLOADABLE_SAMPLE_TITLE_SAMPLE_ID_STORE_ID": true - } - }, - "catalog_product_index_price_downlod_idx": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true - }, - "constraint": { - "PRIMARY": true - } - }, - "catalog_product_index_price_downlod_tmp": { - "column": { - "entity_id": true, - "customer_group_id": true, - "website_id": true, - "min_price": true, - "max_price": true - }, - "constraint": { - "PRIMARY": true + "downloadable_link": { + "column": { + "link_id": true, + "product_id": true, + "sort_order": true, + "number_of_downloads": true, + "is_shareable": true, + "link_url": true, + "link_file": true, + "link_type": true, + "sample_url": true, + "sample_file": true, + "sample_type": true + }, + "index": { + "DOWNLOADABLE_LINK_PRODUCT_ID_SORT_ORDER": true + }, + "constraint": { + "PRIMARY": true, + "DOWNLOADABLE_LINK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true + } + }, + "downloadable_link_price": { + "column": { + "price_id": true, + "link_id": true, + "website_id": true, + "price": true + }, + "index": { + "DOWNLOADABLE_LINK_PRICE_LINK_ID": true, + "DOWNLOADABLE_LINK_PRICE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "DOWNLOADABLE_LINK_PRICE_LINK_ID_DOWNLOADABLE_LINK_LINK_ID": true, + "DOWNLOADABLE_LINK_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "downloadable_link_purchased": { + "column": { + "purchased_id": true, + "order_id": true, + "order_increment_id": true, + "order_item_id": true, + "created_at": true, + "updated_at": true, + "customer_id": true, + "product_name": true, + "product_sku": true, + "link_section_title": true + }, + "index": { + "DOWNLOADABLE_LINK_PURCHASED_ORDER_ID": true, + "DOWNLOADABLE_LINK_PURCHASED_ORDER_ITEM_ID": true, + "DOWNLOADABLE_LINK_PURCHASED_CUSTOMER_ID": true + }, + "constraint": { + "PRIMARY": true, + "DL_LNK_PURCHASED_CSTR_ID_CSTR_ENTT_ENTT_ID": true, + "DOWNLOADABLE_LINK_PURCHASED_ORDER_ID_SALES_ORDER_ENTITY_ID": true + } + }, + "downloadable_link_purchased_item": { + "column": { + "item_id": true, + "purchased_id": true, + "order_item_id": true, + "product_id": true, + "link_hash": true, + "number_of_downloads_bought": true, + "number_of_downloads_used": true, + "link_id": true, + "link_title": true, + "is_shareable": true, + "link_url": true, + "link_file": true, + "link_type": true, + "status": true, + "created_at": true, + "updated_at": true + }, + "index": { + "DOWNLOADABLE_LINK_PURCHASED_ITEM_LINK_HASH": true, + "DOWNLOADABLE_LINK_PURCHASED_ITEM_ORDER_ITEM_ID": true, + "DOWNLOADABLE_LINK_PURCHASED_ITEM_PURCHASED_ID": true + }, + "constraint": { + "PRIMARY": true, + "DL_LNK_PURCHASED_ITEM_PURCHASED_ID_DL_LNK_PURCHASED_PURCHASED_ID": true, + "DL_LNK_PURCHASED_ITEM_ORDER_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true + } + }, + "downloadable_link_title": { + "column": { + "title_id": true, + "link_id": true, + "store_id": true, + "title": true + }, + "index": { + "DOWNLOADABLE_LINK_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "DOWNLOADABLE_LINK_TITLE_LINK_ID_DOWNLOADABLE_LINK_LINK_ID": true, + "DOWNLOADABLE_LINK_TITLE_STORE_ID_STORE_STORE_ID": true, + "DOWNLOADABLE_LINK_TITLE_LINK_ID_STORE_ID": true + } + }, + "downloadable_sample": { + "column": { + "sample_id": true, + "product_id": true, + "sample_url": true, + "sample_file": true, + "sample_type": true, + "sort_order": true + }, + "index": { + "DOWNLOADABLE_SAMPLE_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "DOWNLOADABLE_SAMPLE_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true + } + }, + "downloadable_sample_title": { + "column": { + "title_id": true, + "sample_id": true, + "store_id": true, + "title": true + }, + "index": { + "DOWNLOADABLE_SAMPLE_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "DL_SAMPLE_TTL_SAMPLE_ID_DL_SAMPLE_SAMPLE_ID": true, + "DOWNLOADABLE_SAMPLE_TITLE_STORE_ID_STORE_STORE_ID": true, + "DOWNLOADABLE_SAMPLE_TITLE_SAMPLE_ID_STORE_ID": true + } + }, + "catalog_product_index_price_downlod_idx": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true + }, + "constraint": { + "PRIMARY": true + } + }, + "catalog_product_index_price_downlod_tmp": { + "column": { + "entity_id": true, + "customer_group_id": true, + "website_id": true, + "min_price": true, + "max_price": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/DownloadableGraphQl/Test/Mftf/composer.json b/app/code/Magento/DownloadableGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 725f17975e581..0000000000000 --- a/app/code/Magento/DownloadableGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-downloadable-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json b/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json deleted file mode 100644 index da5caa42b87d5..0000000000000 --- a/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-downloadable-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Eav/Api/Data/AttributeInterface.php b/app/code/Magento/Eav/Api/Data/AttributeInterface.php index e8970d2b42149..55d6e58b64b71 100644 --- a/app/code/Magento/Eav/Api/Data/AttributeInterface.php +++ b/app/code/Magento/Eav/Api/Data/AttributeInterface.php @@ -6,12 +6,15 @@ */ namespace Magento\Eav\Api\Data; +use Magento\Framework\Api\CustomAttributesDataInterface; +use Magento\Framework\Api\MetadataObjectInterface; + /** * Interface AttributeInterface * @api * @since 100.0.2 */ -interface AttributeInterface extends \Magento\Framework\Api\CustomAttributesDataInterface +interface AttributeInterface extends CustomAttributesDataInterface, MetadataObjectInterface { const ATTRIBUTE_ID = 'attribute_id'; diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Js.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Js.php index 8ac79a0aa0d49..7dd6b0a19ec02 100644 --- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Js.php +++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Js.php @@ -18,7 +18,7 @@ class Js extends \Magento\Backend\Block\Template * @var string */ - protected $_template = 'attribute/edit/js.phtml'; + protected $_template = 'Magento_Eav::attribute/edit/js.phtml'; /** * @var \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Text.php b/app/code/Magento/Eav/Model/Attribute/Data/Text.php index 242f4e98108c9..f81fb2affd3b3 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/Text.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/Text.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Model\Attribute\Data; use Magento\Framework\App\RequestInterface; @@ -76,19 +77,9 @@ public function validateValue($value) return true; } - // validate length - $length = $this->_string->strlen(trim($value)); - - $validateRules = $attribute->getValidateRules(); - if (!empty($validateRules['min_text_length']) && $length < $validateRules['min_text_length']) { - $label = __($attribute->getStoreLabel()); - $v = $validateRules['min_text_length']; - $errors[] = __('"%1" length must be equal or greater than %2 characters.', $label, $v); - } - if (!empty($validateRules['max_text_length']) && $length > $validateRules['max_text_length']) { - $label = __($attribute->getStoreLabel()); - $v = $validateRules['max_text_length']; - $errors[] = __('"%1" length must be equal or less than %2 characters.', $label, $v); + $result = $this->validateLength($attribute, $value); + if (count($result) !== 0) { + $errors = array_merge($errors, $result); } $result = $this->_validateInputRule($value); @@ -142,4 +133,33 @@ public function outputValue($format = \Magento\Eav\Model\AttributeDataFactory::O return $value; } + + /** + * Validates value length by attribute rules + * + * @param \Magento\Eav\Model\Attribute $attribute + * @param string $value + * @return array errors + */ + private function validateLength(\Magento\Eav\Model\Attribute $attribute, $value): array + { + $errors = []; + $length = $this->_string->strlen(trim($value)); + $validateRules = $attribute->getValidateRules(); + + if (!empty($validateRules['input_validation'])) { + if (!empty($validateRules['min_text_length']) && $length < $validateRules['min_text_length']) { + $label = __($attribute->getStoreLabel()); + $v = $validateRules['min_text_length']; + $errors[] = __('"%1" length must be equal or greater than %2 characters.', $label, $v); + } + if (!empty($validateRules['max_text_length']) && $length > $validateRules['max_text_length']) { + $label = __($attribute->getStoreLabel()); + $v = $validateRules['max_text_length']; + $errors[] = __('"%1" length must be equal or less than %2 characters.', $label, $v); + } + } + + return $errors; + } } diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index 7159cedb61cee..0522ea0432176 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -591,13 +591,7 @@ public function attributesCompare($firstAttribute, $secondAttribute) $firstSort = $firstAttribute->getSortWeight((int) $this->_sortingSetId); $secondSort = $secondAttribute->getSortWeight((int) $this->_sortingSetId); - if ($firstSort > $secondSort) { - return 1; - } elseif ($firstSort < $secondSort) { - return -1; - } - - return 0; + return $firstSort <=> $secondSort; } /** diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index 1b20858a0c424..c605f3ce17e30 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -8,6 +8,7 @@ use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; /** @@ -284,12 +285,10 @@ public function beforeSave() // save default date value as timestamp if ($hasDefaultValue) { - $format = $this->_localeDate->getDateFormat( - \IntlDateFormatter::SHORT - ); try { - $defaultValue = $this->dateTimeFormatter->formatObject(new \DateTime($defaultValue), $format); - $this->setDefaultValue($defaultValue); + $locale = $this->_localeResolver->getLocale(); + $defaultValue = $this->_localeDate->date($defaultValue, $locale, false, false); + $this->setDefaultValue($defaultValue->format(DateTime::DATETIME_PHP_FORMAT)); } catch (\Exception $e) { throw new LocalizedException(__('The default date is invalid. Verify the date and try again.')); } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index f5c7d88919f3c..6601c05051378 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -119,6 +119,11 @@ abstract class AbstractAttribute extends \Magento\Framework\Model\AbstractExtens */ protected $dataObjectHelper; + /** + * @var FrontendLabelFactory + */ + private $frontendLabelFactory; + /** * Serializer Instance. * @@ -163,6 +168,7 @@ abstract class AbstractAttribute extends \Magento\Framework\Model\AbstractExtens * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data * @param \Magento\Eav\Api\Data\AttributeExtensionFactory|null $eavExtensionFactory + * @param FrontendLabelFactory|null $frontendLabelFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @codeCoverageIgnore */ @@ -182,7 +188,8 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - \Magento\Eav\Api\Data\AttributeExtensionFactory $eavExtensionFactory = null + \Magento\Eav\Api\Data\AttributeExtensionFactory $eavExtensionFactory = null, + FrontendLabelFactory $frontendLabelFactory = null ) { parent::__construct( $context, @@ -203,6 +210,8 @@ public function __construct( $this->dataObjectHelper = $dataObjectHelper; $this->eavExtensionFactory = $eavExtensionFactory ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Eav\Api\Data\AttributeExtensionFactory::class); + $this->frontendLabelFactory = $frontendLabelFactory + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(FrontendLabelFactory::class); } /** @@ -1234,6 +1243,19 @@ public function setDefaultFrontendLabel($defaultFrontendLabel) */ public function getFrontendLabels() { + if ($this->getData(self::FRONTEND_LABELS) == null) { + $attributeId = $this->getAttributeId(); + $storeLabels = $this->_getResource()->getStoreLabelsByAttributeId($attributeId); + + $resultFrontedLabels = []; + foreach ($storeLabels as $i => $label) { + $frontendLabel = $this->frontendLabelFactory->create(); + $frontendLabel->setStoreId($i); + $frontendLabel->setLabel($label); + $resultFrontedLabels[] = $frontendLabel; + } + $this->setData(self::FRONTEND_LABELS, $resultFrontedLabels); + } return $this->_getData(self::FRONTEND_LABELS); } diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 25858f6a3454d..88f58c6d0111c 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -171,10 +171,10 @@ protected function _beforeSave(AbstractModel $object) { $frontendLabel = $object->getFrontendLabel(); if (is_array($frontendLabel)) { - if (!isset($frontendLabel[0]) || $frontendLabel[0] === null || $frontendLabel[0] == '') { - throw new \Magento\Framework\Exception\LocalizedException(__('The storefront label is not defined.')); - } + $this->checkDefaultFrontendLabelExists($frontendLabel, $frontendLabel); $object->setFrontendLabel($frontendLabel[0])->setStoreLabels($frontendLabel); + } else { + $this->setStoreLabels($object, $frontendLabel); } /** @@ -742,4 +742,43 @@ public function __wakeup() $this->_storeManager = \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Store\Model\StoreManagerInterface::class); } + + /** + * This method extracts frontend labels into array and sets array values as storeLabels into an object. + * + * @param AbstractModel $object + * @param string|null $frontendLabel + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function setStoreLabels(AbstractModel $object, $frontendLabel) + { + $resultLabel = []; + $frontendLabels = $object->getFrontendLabels(); + if (isset($frontendLabels[0]) + && $frontendLabels[0] instanceof \Magento\Eav\Model\Entity\Attribute\FrontendLabel + ) { + foreach ($frontendLabels as $label) { + $resultLabel[$label->getStoreId()] = $label->getLabel(); + } + $this->checkDefaultFrontendLabelExists($frontendLabel, $resultLabel); + $object->setStoreLabels($resultLabel); + } + } + + /** + * This method checks whether value for default frontend label exists in attribute data. + * + * @param array|string|null $frontendLabel + * @param array $resultLabels + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function checkDefaultFrontendLabelExists($frontendLabel, $resultLabels) + { + $isAdminStoreLabel = (isset($resultLabels[0]) && !empty($resultLabels[0])); + if (empty($frontendLabel) && !$isAdminStoreLabel) { + throw new \Magento\Framework\Exception\LocalizedException(__('The storefront label is not defined.')); + } + } } diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php index 492da0b72c122..cec415e513677 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php @@ -222,7 +222,7 @@ public function setInAllAttributeSetsFilter(array $setIds) $setIds ) ->group('entity_attribute.attribute_id') - ->having('count = ' . count($setIds)); + ->having(new \Zend_Db_Expr('COUNT(*)') . ' = ' . count($setIds)); } //$this->getSelect()->distinct(true); diff --git a/app/code/Magento/Eav/Test/Mftf/composer.json b/app/code/Magento/Eav/Test/Mftf/composer.json deleted file mode 100644 index 8ad451d607263..0000000000000 --- a/app/code/Magento/Eav/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-eav", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/MultilineTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/MultilineTest.php index f628d52f6647f..bde4a3adb9de5 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/MultilineTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/MultilineTest.php @@ -3,8 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Test\Unit\Model\Attribute\Data; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; + class MultilineTest extends \PHPUnit\Framework\TestCase { /** @@ -13,15 +17,21 @@ class MultilineTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Stdlib\StringUtils */ protected $stringMock; + /** + * {@inheritDoc} + */ protected function setUp() { - $timezoneMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); + /** @var TimezoneInterface $timezoneMock */ + $timezoneMock = $this->createMock(TimezoneInterface::class); + /** @var \Psr\Log\LoggerInterface $loggerMock */ $loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $localeResolverMock = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); + /** @var ResolverInterface $localeResolverMock */ + $localeResolverMock = $this->createMock(ResolverInterface::class); $this->stringMock = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); $this->model = new \Magento\Eav\Model\Attribute\Data\Multiline( @@ -33,7 +43,7 @@ protected function setUp() } /** - * @covers \Magento\Eav\Model\Attribute\Data\Multiline::extractValue + * @covers \Magento\Eav\Model\Attribute\Data\Multiline::extractValue * * @param mixed $param * @param mixed $expectedResult @@ -41,11 +51,15 @@ protected function setUp() */ public function testExtractValue($param, $expectedResult) { + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\App\RequestInterface $requestMock */ $requestMock = $this->createMock(\Magento\Framework\App\RequestInterface::class); + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Eav\Model\Attribute $attributeMock */ $attributeMock = $this->createMock(\Magento\Eav\Model\Attribute::class); $requestMock->expects($this->once())->method('getParam')->will($this->returnValue($param)); - $attributeMock->expects($this->once())->method('getAttributeCode')->will($this->returnValue('attributeCode')); + $attributeMock->expects($this->once()) + ->method('getAttributeCode') + ->will($this->returnValue('attributeCode')); $this->model->setAttribute($attributeMock); $this->assertEquals($expectedResult, $this->model->extractValue($requestMock)); @@ -69,7 +83,7 @@ public function extractValueDataProvider() } /** - * @covers \Magento\Eav\Model\Attribute\Data\Multiline::outputValue + * @covers \Magento\Eav\Model\Attribute\Data\Multiline::outputValue * * @param string $format * @param mixed $expectedResult @@ -77,9 +91,13 @@ public function extractValueDataProvider() */ public function testOutputValue($format, $expectedResult) { + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Model\AbstractModel $entityMock */ $entityMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); - $entityMock->expects($this->once())->method('getData')->will($this->returnValue("value1\nvalue2")); + $entityMock->expects($this->once()) + ->method('getData') + ->will($this->returnValue("value1\nvalue2")); + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Eav\Model\Attribute $attributeMock */ $attributeMock = $this->createMock(\Magento\Eav\Model\Attribute::class); $this->model->setEntity($entityMock); @@ -113,8 +131,8 @@ public function outputValueDataProvider() } /** - * @covers \Magento\Eav\Model\Attribute\Data\Multiline::validateValue - * @covers \Magento\Eav\Model\Attribute\Data\Text::validateValue + * @covers \Magento\Eav\Model\Attribute\Data\Multiline::validateValue + * @covers \Magento\Eav\Model\Attribute\Data\Text::validateValue * * @param mixed $value * @param bool $isAttributeRequired @@ -124,14 +142,23 @@ public function outputValueDataProvider() */ public function testValidateValue($value, $isAttributeRequired, $rules, $expectedResult) { + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Model\AbstractModel $entityMock */ $entityMock = $this->createMock(\Magento\Framework\Model\AbstractModel::class); - $entityMock->expects($this->any())->method('getDataUsingMethod')->will($this->returnValue("value1\nvalue2")); + $entityMock->expects($this->any()) + ->method('getDataUsingMethod') + ->will($this->returnValue("value1\nvalue2")); + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Eav\Model\Attribute $attributeMock */ $attributeMock = $this->createMock(\Magento\Eav\Model\Attribute::class); $attributeMock->expects($this->any())->method('getMultilineCount')->will($this->returnValue(2)); $attributeMock->expects($this->any())->method('getValidateRules')->will($this->returnValue($rules)); - $attributeMock->expects($this->any())->method('getStoreLabel')->will($this->returnValue('Label')); - $attributeMock->expects($this->any())->method('getIsRequired')->will($this->returnValue($isAttributeRequired)); + $attributeMock->expects($this->any()) + ->method('getStoreLabel') + ->will($this->returnValue('Label')); + + $attributeMock->expects($this->any()) + ->method('getIsRequired') + ->will($this->returnValue($isAttributeRequired)); $this->stringMock->expects($this->any())->method('strlen')->will($this->returnValue(5)); @@ -159,7 +186,7 @@ public function validateValueDataProvider() 'expectedResult' => true, ], [ - 'value' => ['value1', 'value2'], + 'value' => ['value1', 'value2'], 'isAttributeRequired' => false, 'rules' => [], 'expectedResult' => true, @@ -167,13 +194,13 @@ public function validateValueDataProvider() [ 'value' => 'value', 'isAttributeRequired' => false, - 'rules' => ['max_text_length' => 3], + 'rules' => ['input_validation' => 'other', 'max_text_length' => 3], 'expectedResult' => ['"Label" length must be equal or less than 3 characters.'], ], [ 'value' => 'value', 'isAttributeRequired' => false, - 'rules' => ['min_text_length' => 10], + 'rules' => ['input_validation' => 'other', 'min_text_length' => 10], 'expectedResult' => ['"Label" length must be equal or greater than 10 characters.'], ], [ diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php index 36eac0bfab27b..bbbe712b2bb42 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Eav\Test\Unit\Model\Attribute\Data; class TextTest extends \PHPUnit\Framework\TestCase @@ -12,6 +13,9 @@ class TextTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * {@inheritDoc} + */ protected function setUp() { $locale = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); @@ -19,14 +23,77 @@ protected function setUp() $logger = $this->createMock(\Psr\Log\LoggerInterface::class); $helper = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); - $attributeData = [ + $this->_model = new \Magento\Eav\Model\Attribute\Data\Text($locale, $logger, $localeResolver, $helper); + $this->_model->setAttribute( + $this->createAttribute( + [ + 'store_label' => 'Test', + 'attribute_code' => 'test', + 'is_required' => 1, + 'validate_rules' => ['min_text_length' => 0, 'max_text_length' => 0, 'input_validation' => 0], + ] + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function tearDown() + { + $this->_model = null; + } + + /** + * Test for string validation + */ + public function testValidateValueString(): void + { + $inputValue = '0'; + $expectedResult = true; + $this->assertEquals($expectedResult, $this->_model->validateValue($inputValue)); + } + + /** + * Test for integer validation + */ + public function testValidateValueInteger(): void + { + $inputValue = 0; + $expectedResult = ['"Test" is a required value.']; + $result = $this->_model->validateValue($inputValue); + $this->assertEquals($expectedResult, [(string)$result[0]]); + } + + /** + * Test without length validation + */ + public function testWithoutLengthValidation(): void + { + $expectedResult = true; + $defaultAttributeData = [ 'store_label' => 'Test', 'attribute_code' => 'test', 'is_required' => 1, 'validate_rules' => ['min_text_length' => 0, 'max_text_length' => 0, 'input_validation' => 0], ]; - $attributeClass = \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class; + $defaultAttributeData['validate_rules']['min_text_length'] = 2; + $this->_model->setAttribute($this->createAttribute($defaultAttributeData)); + $this->assertEquals($expectedResult, $this->_model->validateValue('t')); + + $defaultAttributeData['validate_rules']['max_text_length'] = 3; + $this->_model->setAttribute($this->createAttribute($defaultAttributeData)); + $this->assertEquals($expectedResult, $this->_model->validateValue('test')); + } + + /** + * @param array $attributeData + * @return \Magento\Eav\Model\Attribute + */ + protected function createAttribute($attributeData): \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + { + $attributeClass = \Magento\Eav\Model\Attribute::class; $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $eavTypeFactory = $this->createMock(\Magento\Eav\Model\Entity\TypeFactory::class); $arguments = $objectManagerHelper->getConstructArguments( @@ -41,27 +108,6 @@ protected function setUp() ->setMethods(['_init']) ->setConstructorArgs($arguments) ->getMock(); - $this->_model = new \Magento\Eav\Model\Attribute\Data\Text($locale, $logger, $localeResolver, $helper); - $this->_model->setAttribute($attribute); - } - - protected function tearDown() - { - $this->_model = null; - } - - public function testValidateValueString() - { - $inputValue = '0'; - $expectedResult = true; - $this->assertEquals($expectedResult, $this->_model->validateValue($inputValue)); - } - - public function testValidateValueInteger() - { - $inputValue = 0; - $expectedResult = ['"Test" is a required value.']; - $result = $this->_model->validateValue($inputValue); - $this->assertEquals($expectedResult, [(string)$result[0]]); + return $attribute; } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php index 911aecf8e7cfb..b15174960524c 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeTest.php @@ -13,11 +13,17 @@ class AttributeTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * @inheritdoc + */ protected function setUp() { $this->_model = $this->createPartialMock(\Magento\Eav\Model\Entity\Attribute::class, ['__wakeup']); } + /** + * @inheritdoc + */ protected function tearDown() { $this->_model = null; @@ -27,6 +33,7 @@ protected function tearDown() * @param string $givenFrontendInput * @param string $expectedBackendType * @dataProvider dataGetBackendTypeByInput + * @return void */ public function testGetBackendTypeByInput($givenFrontendInput, $expectedBackendType) { @@ -112,4 +119,42 @@ public function getSortWeightDataProvider() ] ]; } + + /** + * return void + */ + public function testGetFrontendLabels() + { + $attributeId = 1; + $storeLabels = ['test_attribute_store1']; + $frontendLabelFactory = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\FrontendLabelFactory::class) + ->setMethods(['create']) + ->getMock(); + $resource = $this->getMockBuilder(\Magento\Eav\Model\ResourceModel\Entity\Attribute::class) + ->setMethods(['getStoreLabelsByAttributeId']) + ->disableOriginalConstructor() + ->getMock(); + $arguments = [ + '_resource' => $resource, + 'frontendLabelFactory' => $frontendLabelFactory, + ]; + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->_model = $objectManager->getObject(\Magento\Eav\Model\Entity\Attribute::class, $arguments); + $this->_model->setAttributeId($attributeId); + + $resource->expects($this->once()) + ->method('getStoreLabelsByAttributeId') + ->with($attributeId) + ->willReturn($storeLabels); + $frontendLabel = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\FrontendLabel::class) + ->setMethods(['setStoreId', 'setLabel']) + ->disableOriginalConstructor() + ->getMock(); + $frontendLabelFactory->expects($this->once()) + ->method('create') + ->willReturn($frontendLabel); + $expectedFrontendLabel[] = $frontendLabel; + + $this->assertEquals($expectedFrontendLabel, $this->_model->getFrontendLabels()); + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Attribute/CollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Attribute/CollectionTest.php index 544cd9893e49e..f074fa22778b0 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Attribute/CollectionTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Attribute/CollectionTest.php @@ -115,7 +115,7 @@ protected function setUp() $this->connectionMock->expects($this->any())->method('quoteIdentifier')->will($this->returnArgument(0)); $this->connectionMock->expects($this->any()) ->method('describeTable') - ->will($this->returnvalueMap( + ->will($this->returnValueMap( [ [ 'some_main_table', diff --git a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/Attribute/CollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/Attribute/CollectionTest.php index 138e1363bb6dd..4a0fd5469767e 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/Attribute/CollectionTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/Attribute/CollectionTest.php @@ -150,8 +150,8 @@ public function testSetInAllAttributeSetsFilter() $this->selectMock->expects($this->atLeastOnce())->method('group')->with('entity_attribute.attribute_id') ->willReturnSelf(); - $this->selectMock->expects($this->atLeastOnce())->method('having')->with('count = ' . count($setIds)) - ->willReturnSelf(); + $this->selectMock->expects($this->atLeastOnce())->method('having') + ->with(new \Zend_Db_Expr('COUNT(*)') . ' = ' . count($setIds))->willReturnSelf(); $this->model->setInAllAttributeSetsFilter($setIds); } diff --git a/app/code/Magento/Eav/etc/db_schema_whitelist.json b/app/code/Magento/Eav/etc/db_schema_whitelist.json index cf06b06d833d5..b3f1aca50df01 100644 --- a/app/code/Magento/Eav/etc/db_schema_whitelist.json +++ b/app/code/Magento/Eav/etc/db_schema_whitelist.json @@ -1,390 +1,390 @@ { - "eav_entity_type": { - "column": { - "entity_type_id": true, - "entity_type_code": true, - "entity_model": true, - "attribute_model": true, - "entity_table": true, - "value_table_prefix": true, - "entity_id_field": true, - "is_data_sharing": true, - "data_sharing_key": true, - "default_attribute_set_id": true, - "increment_model": true, - "increment_per_store": true, - "increment_pad_length": true, - "increment_pad_char": true, - "additional_attribute_table": true, - "entity_attribute_collection": true - }, - "index": { - "EAV_ENTITY_TYPE_ENTITY_TYPE_CODE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "eav_entity": { - "column": { - "entity_id": true, - "entity_type_id": true, - "attribute_set_id": true, - "increment_id": true, - "parent_id": true, - "store_id": true, - "created_at": true, - "updated_at": true, - "is_active": true - }, - "index": { - "EAV_ENTITY_ENTITY_TYPE_ID": true, - "EAV_ENTITY_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_STORE_ID_STORE_STORE_ID": true - } - }, - "eav_entity_datetime": { - "column": { - "value_id": true, - "entity_type_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "EAV_ENTITY_DATETIME_STORE_ID": true, - "EAV_ENTITY_DATETIME_ATTRIBUTE_ID_VALUE": true, - "EAV_ENTITY_DATETIME_ENTITY_TYPE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_DATETIME_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, - "EAV_ENTT_DTIME_ENTT_TYPE_ID_EAV_ENTT_TYPE_ENTT_TYPE_ID": true, - "EAV_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, - "EAV_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "eav_entity_decimal": { - "column": { - "value_id": true, - "entity_type_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "EAV_ENTITY_DECIMAL_STORE_ID": true, - "EAV_ENTITY_DECIMAL_ATTRIBUTE_ID_VALUE": true, - "EAV_ENTITY_DECIMAL_ENTITY_TYPE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_DECIMAL_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, - "EAV_ENTITY_DECIMAL_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, - "EAV_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "eav_entity_int": { - "column": { - "value_id": true, - "entity_type_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "EAV_ENTITY_INT_STORE_ID": true, - "EAV_ENTITY_INT_ATTRIBUTE_ID_VALUE": true, - "EAV_ENTITY_INT_ENTITY_TYPE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_INT_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, - "EAV_ENTITY_INT_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, - "EAV_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "eav_entity_text": { - "column": { - "value_id": true, - "entity_type_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "EAV_ENTITY_TEXT_ENTITY_TYPE_ID": true, - "EAV_ENTITY_TEXT_ATTRIBUTE_ID": true, - "EAV_ENTITY_TEXT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_TEXT_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, - "EAV_ENTITY_TEXT_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, - "EAV_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "eav_entity_varchar": { - "column": { - "value_id": true, - "entity_type_id": true, - "attribute_id": true, - "store_id": true, - "entity_id": true, - "value": true - }, - "index": { - "EAV_ENTITY_VARCHAR_STORE_ID": true, - "EAV_ENTITY_VARCHAR_ATTRIBUTE_ID_VALUE": true, - "EAV_ENTITY_VARCHAR_ENTITY_TYPE_ID_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_VARCHAR_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, - "EAV_ENTITY_VARCHAR_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, - "EAV_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true - } - }, - "eav_attribute": { - "column": { - "attribute_id": true, - "entity_type_id": true, - "attribute_code": true, - "attribute_model": true, - "backend_model": true, - "backend_type": true, - "backend_table": true, - "frontend_model": true, - "frontend_input": true, - "frontend_label": true, - "frontend_class": true, - "source_model": true, - "is_required": true, - "is_user_defined": true, - "default_value": true, - "is_unique": true, - "note": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTRIBUTE_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ATTRIBUTE_ENTITY_TYPE_ID_ATTRIBUTE_CODE": true - } - }, - "eav_entity_store": { - "column": { - "entity_store_id": true, - "entity_type_id": true, - "store_id": true, - "increment_prefix": true, - "increment_last_id": true - }, - "index": { - "EAV_ENTITY_STORE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_STORE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_STORE_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ENTITY_STORE_STORE_ID_STORE_STORE_ID": true - } - }, - "eav_attribute_set": { - "column": { - "attribute_set_id": true, - "entity_type_id": true, - "attribute_set_name": true, - "sort_order": true - }, - "index": { - "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_SORT_ORDER": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, - "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_ATTRIBUTE_SET_NAME": true - } - }, - "eav_attribute_group": { - "column": { - "attribute_group_id": true, - "attribute_set_id": true, - "attribute_group_name": true, - "sort_order": true, - "default_id": true, - "attribute_group_code": true, - "tab_group_code": true - }, - "index": { - "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_SORT_ORDER": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTR_GROUP_ATTR_SET_ID_EAV_ATTR_SET_ATTR_SET_ID": true, - "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_NAME": true, - "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_CODE": true, - "CATALOG_CATEGORY_PRODUCT_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_CODE": true - } - }, - "eav_entity_attribute": { - "column": { - "entity_attribute_id": true, - "entity_type_id": true, - "attribute_set_id": true, - "attribute_group_id": true, - "attribute_id": true, - "sort_order": true - }, - "index": { - "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_SET_ID_SORT_ORDER": true, - "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "EAV_ENTT_ATTR_ATTR_GROUP_ID_EAV_ATTR_GROUP_ATTR_GROUP_ID": true, - "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_SET_ID_ATTRIBUTE_ID": true, - "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_GROUP_ID_ATTRIBUTE_ID": true - } - }, - "eav_attribute_option": { - "column": { - "option_id": true, - "attribute_id": true, - "sort_order": true - }, - "index": { - "EAV_ATTRIBUTE_OPTION_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTRIBUTE_OPTION_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true - } - }, - "eav_attribute_option_value": { - "column": { - "value_id": true, - "option_id": true, - "store_id": true, - "value": true - }, - "index": { - "EAV_ATTRIBUTE_OPTION_VALUE_OPTION_ID": true, - "EAV_ATTRIBUTE_OPTION_VALUE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTR_OPT_VAL_OPT_ID_EAV_ATTR_OPT_OPT_ID": true, - "EAV_ATTRIBUTE_OPTION_VALUE_STORE_ID_STORE_STORE_ID": true - } - }, - "eav_attribute_label": { - "column": { - "attribute_label_id": true, - "attribute_id": true, - "store_id": true, - "value": true - }, - "index": { - "EAV_ATTRIBUTE_LABEL_STORE_ID": true, - "EAV_ATTRIBUTE_LABEL_ATTRIBUTE_ID_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_ATTRIBUTE_LABEL_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "EAV_ATTRIBUTE_LABEL_STORE_ID_STORE_STORE_ID": true - } - }, - "eav_form_type": { - "column": { - "type_id": true, - "code": true, - "label": true, - "is_system": true, - "theme": true, - "store_id": true - }, - "index": { - "EAV_FORM_TYPE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_FORM_TYPE_STORE_ID_STORE_STORE_ID": true, - "EAV_FORM_TYPE_CODE_THEME_STORE_ID": true - } - }, - "eav_form_type_entity": { - "column": { - "type_id": true, - "entity_type_id": true - }, - "index": { - "EAV_FORM_TYPE_ENTITY_ENTITY_TYPE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_FORM_TYPE_ENTT_ENTT_TYPE_ID_EAV_ENTT_TYPE_ENTT_TYPE_ID": true, - "EAV_FORM_TYPE_ENTITY_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true - } - }, - "eav_form_fieldset": { - "column": { - "fieldset_id": true, - "type_id": true, - "code": true, - "sort_order": true - }, - "constraint": { - "PRIMARY": true, - "EAV_FORM_FIELDSET_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true, - "EAV_FORM_FIELDSET_TYPE_ID_CODE": true - } - }, - "eav_form_fieldset_label": { - "column": { - "fieldset_id": true, - "store_id": true, - "label": true - }, - "index": { - "EAV_FORM_FIELDSET_LABEL_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_FORM_FSET_LBL_FSET_ID_EAV_FORM_FSET_FSET_ID": true, - "EAV_FORM_FIELDSET_LABEL_STORE_ID_STORE_STORE_ID": true - } - }, - "eav_form_element": { - "column": { - "element_id": true, - "type_id": true, - "fieldset_id": true, - "attribute_id": true, - "sort_order": true - }, - "index": { - "EAV_FORM_ELEMENT_FIELDSET_ID": true, - "EAV_FORM_ELEMENT_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "EAV_FORM_ELEMENT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "EAV_FORM_ELEMENT_FIELDSET_ID_EAV_FORM_FIELDSET_FIELDSET_ID": true, - "EAV_FORM_ELEMENT_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true, - "EAV_FORM_ELEMENT_TYPE_ID_ATTRIBUTE_ID": true + "eav_entity_type": { + "column": { + "entity_type_id": true, + "entity_type_code": true, + "entity_model": true, + "attribute_model": true, + "entity_table": true, + "value_table_prefix": true, + "entity_id_field": true, + "is_data_sharing": true, + "data_sharing_key": true, + "default_attribute_set_id": true, + "increment_model": true, + "increment_per_store": true, + "increment_pad_length": true, + "increment_pad_char": true, + "additional_attribute_table": true, + "entity_attribute_collection": true + }, + "index": { + "EAV_ENTITY_TYPE_ENTITY_TYPE_CODE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "eav_entity": { + "column": { + "entity_id": true, + "entity_type_id": true, + "attribute_set_id": true, + "increment_id": true, + "parent_id": true, + "store_id": true, + "created_at": true, + "updated_at": true, + "is_active": true + }, + "index": { + "EAV_ENTITY_ENTITY_TYPE_ID": true, + "EAV_ENTITY_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_STORE_ID_STORE_STORE_ID": true + } + }, + "eav_entity_datetime": { + "column": { + "value_id": true, + "entity_type_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "EAV_ENTITY_DATETIME_STORE_ID": true, + "EAV_ENTITY_DATETIME_ATTRIBUTE_ID_VALUE": true, + "EAV_ENTITY_DATETIME_ENTITY_TYPE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_DATETIME_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, + "EAV_ENTT_DTIME_ENTT_TYPE_ID_EAV_ENTT_TYPE_ENTT_TYPE_ID": true, + "EAV_ENTITY_DATETIME_STORE_ID_STORE_STORE_ID": true, + "EAV_ENTITY_DATETIME_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "eav_entity_decimal": { + "column": { + "value_id": true, + "entity_type_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "EAV_ENTITY_DECIMAL_STORE_ID": true, + "EAV_ENTITY_DECIMAL_ATTRIBUTE_ID_VALUE": true, + "EAV_ENTITY_DECIMAL_ENTITY_TYPE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_DECIMAL_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, + "EAV_ENTITY_DECIMAL_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_DECIMAL_STORE_ID_STORE_STORE_ID": true, + "EAV_ENTITY_DECIMAL_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "eav_entity_int": { + "column": { + "value_id": true, + "entity_type_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "EAV_ENTITY_INT_STORE_ID": true, + "EAV_ENTITY_INT_ATTRIBUTE_ID_VALUE": true, + "EAV_ENTITY_INT_ENTITY_TYPE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_INT_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, + "EAV_ENTITY_INT_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_INT_STORE_ID_STORE_STORE_ID": true, + "EAV_ENTITY_INT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "eav_entity_text": { + "column": { + "value_id": true, + "entity_type_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "EAV_ENTITY_TEXT_ENTITY_TYPE_ID": true, + "EAV_ENTITY_TEXT_ATTRIBUTE_ID": true, + "EAV_ENTITY_TEXT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_TEXT_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, + "EAV_ENTITY_TEXT_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_TEXT_STORE_ID_STORE_STORE_ID": true, + "EAV_ENTITY_TEXT_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "eav_entity_varchar": { + "column": { + "value_id": true, + "entity_type_id": true, + "attribute_id": true, + "store_id": true, + "entity_id": true, + "value": true + }, + "index": { + "EAV_ENTITY_VARCHAR_STORE_ID": true, + "EAV_ENTITY_VARCHAR_ATTRIBUTE_ID_VALUE": true, + "EAV_ENTITY_VARCHAR_ENTITY_TYPE_ID_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_VARCHAR_ENTITY_ID_EAV_ENTITY_ENTITY_ID": true, + "EAV_ENTITY_VARCHAR_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_VARCHAR_STORE_ID_STORE_STORE_ID": true, + "EAV_ENTITY_VARCHAR_ENTITY_ID_ATTRIBUTE_ID_STORE_ID": true + } + }, + "eav_attribute": { + "column": { + "attribute_id": true, + "entity_type_id": true, + "attribute_code": true, + "attribute_model": true, + "backend_model": true, + "backend_type": true, + "backend_table": true, + "frontend_model": true, + "frontend_input": true, + "frontend_label": true, + "frontend_class": true, + "source_model": true, + "is_required": true, + "is_user_defined": true, + "default_value": true, + "is_unique": true, + "note": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTRIBUTE_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ATTRIBUTE_ENTITY_TYPE_ID_ATTRIBUTE_CODE": true + } + }, + "eav_entity_store": { + "column": { + "entity_store_id": true, + "entity_type_id": true, + "store_id": true, + "increment_prefix": true, + "increment_last_id": true + }, + "index": { + "EAV_ENTITY_STORE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_STORE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_STORE_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ENTITY_STORE_STORE_ID_STORE_STORE_ID": true + } + }, + "eav_attribute_set": { + "column": { + "attribute_set_id": true, + "entity_type_id": true, + "attribute_set_name": true, + "sort_order": true + }, + "index": { + "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_SORT_ORDER": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_EAV_ENTITY_TYPE_ENTITY_TYPE_ID": true, + "EAV_ATTRIBUTE_SET_ENTITY_TYPE_ID_ATTRIBUTE_SET_NAME": true + } + }, + "eav_attribute_group": { + "column": { + "attribute_group_id": true, + "attribute_set_id": true, + "attribute_group_name": true, + "sort_order": true, + "default_id": true, + "attribute_group_code": true, + "tab_group_code": true + }, + "index": { + "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_SORT_ORDER": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTR_GROUP_ATTR_SET_ID_EAV_ATTR_SET_ATTR_SET_ID": true, + "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_NAME": true, + "EAV_ATTRIBUTE_GROUP_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_CODE": true, + "CATALOG_CATEGORY_PRODUCT_ATTRIBUTE_SET_ID_ATTRIBUTE_GROUP_CODE": true + } + }, + "eav_entity_attribute": { + "column": { + "entity_attribute_id": true, + "entity_type_id": true, + "attribute_set_id": true, + "attribute_group_id": true, + "attribute_id": true, + "sort_order": true + }, + "index": { + "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_SET_ID_SORT_ORDER": true, + "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "EAV_ENTT_ATTR_ATTR_GROUP_ID_EAV_ATTR_GROUP_ATTR_GROUP_ID": true, + "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_SET_ID_ATTRIBUTE_ID": true, + "EAV_ENTITY_ATTRIBUTE_ATTRIBUTE_GROUP_ID_ATTRIBUTE_ID": true + } + }, + "eav_attribute_option": { + "column": { + "option_id": true, + "attribute_id": true, + "sort_order": true + }, + "index": { + "EAV_ATTRIBUTE_OPTION_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTRIBUTE_OPTION_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true + } + }, + "eav_attribute_option_value": { + "column": { + "value_id": true, + "option_id": true, + "store_id": true, + "value": true + }, + "index": { + "EAV_ATTRIBUTE_OPTION_VALUE_OPTION_ID": true, + "EAV_ATTRIBUTE_OPTION_VALUE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTR_OPT_VAL_OPT_ID_EAV_ATTR_OPT_OPT_ID": true, + "EAV_ATTRIBUTE_OPTION_VALUE_STORE_ID_STORE_STORE_ID": true + } + }, + "eav_attribute_label": { + "column": { + "attribute_label_id": true, + "attribute_id": true, + "store_id": true, + "value": true + }, + "index": { + "EAV_ATTRIBUTE_LABEL_STORE_ID": true, + "EAV_ATTRIBUTE_LABEL_ATTRIBUTE_ID_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTRIBUTE_LABEL_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "EAV_ATTRIBUTE_LABEL_STORE_ID_STORE_STORE_ID": true + } + }, + "eav_form_type": { + "column": { + "type_id": true, + "code": true, + "label": true, + "is_system": true, + "theme": true, + "store_id": true + }, + "index": { + "EAV_FORM_TYPE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_FORM_TYPE_STORE_ID_STORE_STORE_ID": true, + "EAV_FORM_TYPE_CODE_THEME_STORE_ID": true + } + }, + "eav_form_type_entity": { + "column": { + "type_id": true, + "entity_type_id": true + }, + "index": { + "EAV_FORM_TYPE_ENTITY_ENTITY_TYPE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_FORM_TYPE_ENTT_ENTT_TYPE_ID_EAV_ENTT_TYPE_ENTT_TYPE_ID": true, + "EAV_FORM_TYPE_ENTITY_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true + } + }, + "eav_form_fieldset": { + "column": { + "fieldset_id": true, + "type_id": true, + "code": true, + "sort_order": true + }, + "constraint": { + "PRIMARY": true, + "EAV_FORM_FIELDSET_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true, + "EAV_FORM_FIELDSET_TYPE_ID_CODE": true + } + }, + "eav_form_fieldset_label": { + "column": { + "fieldset_id": true, + "store_id": true, + "label": true + }, + "index": { + "EAV_FORM_FIELDSET_LABEL_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_FORM_FSET_LBL_FSET_ID_EAV_FORM_FSET_FSET_ID": true, + "EAV_FORM_FIELDSET_LABEL_STORE_ID_STORE_STORE_ID": true + } + }, + "eav_form_element": { + "column": { + "element_id": true, + "type_id": true, + "fieldset_id": true, + "attribute_id": true, + "sort_order": true + }, + "index": { + "EAV_FORM_ELEMENT_FIELDSET_ID": true, + "EAV_FORM_ELEMENT_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_FORM_ELEMENT_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "EAV_FORM_ELEMENT_FIELDSET_ID_EAV_FORM_FIELDSET_FIELDSET_ID": true, + "EAV_FORM_ELEMENT_TYPE_ID_EAV_FORM_TYPE_TYPE_ID": true, + "EAV_FORM_ELEMENT_TYPE_ID_ATTRIBUTE_ID": true + } } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php new file mode 100644 index 0000000000000..6ccd610bead0d --- /dev/null +++ b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\EavGraphQl\Model\Resolver; + +use Magento\EavGraphQl\Model\Resolver\DataProvider\AttributeOptions as AttributeOptionsDataProvider; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\StateException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Resolve attribute options data for custom attribute. + */ +class AttributeOptions implements ResolverInterface +{ + /** + * @var AttributeOptionsDataProvider + */ + private $attributeOptionsDataProvider; + + /** + * @var AttributeOptions + */ + private $valueFactory; + + /** + * @param AttributeOptionsDataProvider $attributeOptionsDataProvider + * @param ValueFactory $valueFactory + */ + public function __construct( + AttributeOptionsDataProvider $attributeOptionsDataProvider, + ValueFactory $valueFactory + ) { + $this->attributeOptionsDataProvider = $attributeOptionsDataProvider; + $this->valueFactory = $valueFactory; + } + + /** + * @inheritDoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) : Value { + + return $this->valueFactory->create(function () use ($value) { + $entityType = $this->getEntityType($value); + $attributeCode = $this->getAttributeCode($value); + + $optionsData = $this->getAttributeOptionsData($entityType, $attributeCode); + return $optionsData; + }); + } + + /** + * @param array $value + * @return int + * @throws GraphQlInputException + */ + private function getEntityType(array $value): int + { + if (!isset($value['entity_type'])) { + throw new GraphQlInputException(__('"Entity type should be specified')); + } + + return (int)$value['entity_type']; + } + + /** + * @param array $value + * @return string + * @throws GraphQlInputException + */ + private function getAttributeCode(array $value): string + { + if (!isset($value['attribute_code'])) { + throw new GraphQlInputException(__('"Attribute code should be specified')); + } + + return $value['attribute_code']; + } + + /** + * @param int $entityType + * @param string $attributeCode + * @return array + * @throws GraphQlInputException + * @throws GraphQlNoSuchEntityException + */ + private function getAttributeOptionsData(int $entityType, string $attributeCode): array + { + try { + $optionsData = $this->attributeOptionsDataProvider->getData($entityType, $attributeCode); + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage()), $e); + } catch (StateException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } + return $optionsData; + } +} diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php b/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php new file mode 100644 index 0000000000000..900a31c1093ed --- /dev/null +++ b/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\EavGraphQl\Model\Resolver\DataProvider; + +use Magento\Eav\Api\AttributeOptionManagementInterface; + +/** + * Attribute Options data provider + */ +class AttributeOptions +{ + /** + * @var AttributeOptionManagementInterface + */ + private $optionManager; + + /** + * @param AttributeOptionManagementInterface $optionManager + */ + public function __construct( + AttributeOptionManagementInterface $optionManager + ) { + $this->optionManager = $optionManager; + } + + /** + * @param int $entityType + * @param string $attributeCode + * @return array + */ + public function getData(int $entityType, string $attributeCode): array + { + $options = $this->optionManager->getItems($entityType, $attributeCode); + + $optionsData = []; + foreach ($options as $option) { + // without empty option @see \Magento\Eav\Model\Entity\Attribute\Source\Table::getAllOptions + if ($option->getValue() === '') { + continue; + } + + $optionsData[] = [ + 'label' => $option->getLabel(), + 'value' => $option->getValue() + ]; + } + return $optionsData; + } +} diff --git a/app/code/Magento/EavGraphQl/Test/Mftf/composer.json b/app/code/Magento/EavGraphQl/Test/Mftf/composer.json deleted file mode 100644 index f0390a211f3dc..0000000000000 --- a/app/code/Magento/EavGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-eav-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/EavGraphQl/composer.json b/app/code/Magento/EavGraphQl/composer.json index 6da27ed27cf36..a2c2d025a3d9d 100644 --- a/app/code/Magento/EavGraphQl/composer.json +++ b/app/code/Magento/EavGraphQl/composer.json @@ -4,7 +4,8 @@ "type": "magento2-module", "require": { "php": "~7.1.3||~7.2.0", - "magento/framework": "*" + "magento/framework": "*", + "magento/module-eav": "*" }, "suggest": { "magento/module-graph-ql": "*" diff --git a/app/code/Magento/EavGraphQl/etc/schema.graphqls b/app/code/Magento/EavGraphQl/etc/schema.graphqls index 7799498c40409..adada3030f501 100644 --- a/app/code/Magento/EavGraphQl/etc/schema.graphqls +++ b/app/code/Magento/EavGraphQl/etc/schema.graphqls @@ -13,6 +13,12 @@ type Attribute @doc(description: "Attribute contains the attribute_type of the s attribute_code: String @doc(description: "The unique identifier for an attribute code. This value should be in lowercase letters without spaces.") entity_type: String @doc(description: "The type of entity that defines the attribute") attribute_type: String @doc(description: "The data type of the attribute") + attribute_options: [AttributeOption] @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributeOptions") @doc(description: "Attribute options list.") +} + +type AttributeOption @doc(description: "Attribute option.") { + label: String @doc(description: "Attribute option label.") + value: String @doc(description: "Attribute option value.") } input AttributeInput @doc(description: "AttributeInput specifies the attribute_code and entity_type to search") { diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php index 8590c9b58786e..09357216d2067 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php @@ -69,7 +69,9 @@ public function __construct( } /** - * {@inheritdoc} + * @param string $attributeCode + * @param array $context + * @return string */ public function getFieldName($attributeCode, $context = []) { @@ -105,8 +107,10 @@ public function getFieldName($attributeCode, $context = []) } /** - * {@inheritdoc} + * @param array $context + * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getAllAttributesTypes($context = []) { @@ -173,8 +177,14 @@ protected function isAttributeUsedInAdvancedSearch($attribute) */ protected function getRefinedFieldName($frontendInput, $fieldType, $attributeCode) { - return (in_array($frontendInput, ['select', 'boolean'], true) && $fieldType === 'integer') - ? $attributeCode . '_value' : $attributeCode; + switch ($frontendInput) { + case 'select': + return in_array($fieldType, ['text','integer'], true) ? $attributeCode . '_value' : $attributeCode; + case 'boolean': + return $fieldType === 'integer' ? $attributeCode . '_value' : $attributeCode; + default: + return $attributeCode; + } } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php index c0ecaadaea504..8ffa5839a125b 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Elasticsearch\Elasticsearch5\Model\Client; use Magento\Framework\Exception\LocalizedException; @@ -15,21 +14,21 @@ class Elasticsearch implements ClientInterface { /** - * Elasticsearch Client instance + * Elasticsearch Client instances * - * @var \Elasticsearch\Client + * @var \Elasticsearch\Client[] */ - protected $client; + private $client; /** * @var array */ - protected $clientOptions; + private $clientOptions; /** * @var bool */ - protected $pingResult; + private $pingResult; /** * @var string @@ -48,7 +47,7 @@ public function __construct( $elasticsearchClient = null ) { if (empty($options['hostname']) || ((!empty($options['enableAuth']) && - ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); @@ -58,10 +57,25 @@ public function __construct( $config = $this->buildConfig($options); $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true); } - $this->client = $elasticsearchClient; + $this->client[getmypid()] = $elasticsearchClient; $this->clientOptions = $options; } + /** + * Get Elasticsearch Client + * + * @return \Elasticsearch\Client + */ + private function getClient() + { + $pid = getmypid(); + if (!isset($this->client[$pid])) { + $config = $this->buildConfig($this->clientOptions); + $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true); + } + return $this->client[$pid]; + } + /** * Ping the Elasticsearch client * @@ -70,7 +84,7 @@ public function __construct( public function ping() { if ($this->pingResult === null) { - $this->pingResult = $this->client->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); + $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); } return $this->pingResult; @@ -116,7 +130,7 @@ private function buildConfig($options = []) */ public function bulkQuery($query) { - $this->client->bulk($query); + $this->getClient()->bulk($query); } /** @@ -128,7 +142,7 @@ public function bulkQuery($query) */ public function createIndex($index, $settings) { - $this->client->indices()->create([ + $this->getClient()->indices()->create([ 'index' => $index, 'body' => $settings, ]); @@ -142,7 +156,7 @@ public function createIndex($index, $settings) */ public function deleteIndex($index) { - $this->client->indices()->delete(['index' => $index]); + $this->getClient()->indices()->delete(['index' => $index]); } /** @@ -153,7 +167,7 @@ public function deleteIndex($index) */ public function isEmptyIndex($index) { - $stats = $this->client->indices()->stats(['index' => $index, 'metric' => 'docs']); + $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) { return true; } @@ -178,7 +192,7 @@ public function updateAlias($alias, $newIndex, $oldIndex = '') $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; } - $this->client->indices()->updateAliases($params); + $this->getClient()->indices()->updateAliases($params); } /** @@ -189,7 +203,7 @@ public function updateAlias($alias, $newIndex, $oldIndex = '') */ public function indexExists($index) { - return $this->client->indices()->exists(['index' => $index]); + return $this->getClient()->indices()->exists(['index' => $index]); } /** @@ -204,7 +218,7 @@ public function existsAlias($alias, $index = '') if ($index) { $params['index'] = $index; } - return $this->client->indices()->existsAlias($params); + return $this->getClient()->indices()->existsAlias($params); } /** @@ -214,7 +228,7 @@ public function existsAlias($alias, $index = '') */ public function getAlias($alias) { - return $this->client->indices()->getAlias(['name' => $alias]); + return $this->getClient()->indices()->getAlias(['name' => $alias]); } /** @@ -244,6 +258,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'float', + 'store' => true, ], ], ], @@ -274,7 +289,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) $params['body'][$entityType]['properties'][$field] = $this->prepareFieldInfo($fieldInfo); } - $this->client->indices()->putMapping($params); + $this->getClient()->indices()->putMapping($params); } /** @@ -311,7 +326,7 @@ private function prepareFieldInfo($fieldInfo) */ public function deleteMapping($index, $entityType) { - $this->client->indices()->deleteMapping([ + $this->getClient()->indices()->deleteMapping([ 'index' => $index, 'type' => $entityType, ]); @@ -327,7 +342,7 @@ public function query($query) { $query = $this->prepareSearchQuery($query); - return $this->client->search($query); + return $this->getClient()->search($query); } /** @@ -358,7 +373,7 @@ private function prepareSearchQuery($query) */ public function suggest($query) { - return $this->client->suggest($query); + return $this->getClient()->suggest($query); } /** @@ -369,7 +384,7 @@ public function suggest($query) private function getServerVersion() { if ($this->serverVersion === null) { - $info = $this->client->info(); + $info = $this->getClient()->info(); $this->serverVersion = $info['version']['number']; } diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php new file mode 100644 index 0000000000000..a1fcbeb061481 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php @@ -0,0 +1,251 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation; + +use Magento\Framework\Search\Dynamic\IntervalInterface; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\CatalogSearch\Model\Indexer\Fulltext; + +/** + * Aggregate price intervals for search query result. + */ +class Interval implements IntervalInterface +{ + /** + * Minimal possible value + */ + const DELTA = 0.005; + + /** + * @var ConnectionManager + */ + private $connectionManager; + + /** + * @var FieldMapperInterface + */ + private $fieldMapper; + + /** + * @var Config + */ + private $clientConfig; + + /** + * @var string + */ + private $fieldName; + + /** + * @var string + */ + private $storeId; + + /** + * @var array + */ + private $entityIds; + + /** + * @var SearchIndexNameResolver + */ + private $searchIndexNameResolver; + + /** + * @param ConnectionManager $connectionManager + * @param FieldMapperInterface $fieldMapper + * @param Config $clientConfig + * @param SearchIndexNameResolver $searchIndexNameResolver + * @param string $fieldName + * @param string $storeId + * @param array $entityIds + */ + public function __construct( + ConnectionManager $connectionManager, + FieldMapperInterface $fieldMapper, + Config $clientConfig, + SearchIndexNameResolver $searchIndexNameResolver, + string $fieldName, + string $storeId, + array $entityIds + ) { + $this->connectionManager = $connectionManager; + $this->fieldMapper = $fieldMapper; + $this->clientConfig = $clientConfig; + $this->fieldName = $fieldName; + $this->storeId = $storeId; + $this->entityIds = $entityIds; + $this->searchIndexNameResolver = $searchIndexNameResolver; + } + + /** + * {@inheritdoc} + */ + public function load($limit, $offset = null, $lower = null, $upper = null) + { + $from = $to = []; + if ($lower) { + $from = ['gte' => $lower - self::DELTA]; + } + if ($upper) { + $to = ['lt' => $upper - self::DELTA]; + } + + $requestQuery = $this->prepareBaseRequestQuery($from, $to); + $requestQuery = array_merge_recursive( + $requestQuery, + ['body' => ['stored_fields' => [$this->fieldName], 'size' => $limit]] + ); + + if ($offset) { + $requestQuery['body']['from'] = $offset; + } + + $queryResult = $this->connectionManager->getConnection() + ->query($requestQuery); + + return $this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName); + } + + /** + * {@inheritdoc} + */ + public function loadPrevious($data, $index, $lower = null) + { + if ($lower) { + $from = ['gte' => $lower - self::DELTA]; + } + if ($data) { + $to = ['lt' => $data - self::DELTA]; + } + + $requestQuery = $this->prepareBaseRequestQuery($from, $to); + $requestQuery = array_merge_recursive( + $requestQuery, + ['size' => 0] + ); + + $queryResult = $this->connectionManager->getConnection() + ->query($requestQuery); + + $offset = $queryResult['hits']['total']; + if (!$offset) { + return false; + } + + return $this->load($index - $offset + 1, $offset - 1, $lower); + } + + /** + * {@inheritdoc} + */ + public function loadNext($data, $rightIndex, $upper = null) + { + $from = ['gt' => $data + self::DELTA]; + $to = ['lt' => $data - self::DELTA]; + + $requestCountQuery = $this->prepareBaseRequestQuery($from, $to); + $requestCountQuery = array_merge_recursive( + $requestCountQuery, + ['size' => 0] + ); + + $queryCountResult = $this->connectionManager->getConnection() + ->query($requestCountQuery); + + $offset = $queryCountResult['hits']['total']; + if (!$offset) { + return false; + } + + $from = ['gte' => $data - self::DELTA]; + if ($upper !== null) { + $to = ['lt' => $data - self::DELTA]; + } + + $requestQuery = $requestCountQuery; + + $requestCountQuery['body']['query']['bool']['filter']['bool']['must']['range'] = + [$this->fieldName => array_merge($from, $to)]; + $requestCountQuery['body']['from'] = $offset - 1; + $requestCountQuery['body']['size'] = $rightIndex - $offset + 1; + $queryResult = $this->connectionManager->getConnection() + ->query($requestQuery); + + return array_reverse($this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName)); + } + + /** + * Conver array values to float type. + * + * @param array $hits + * @param string $fieldName + * + * @return float[] + */ + private function arrayValuesToFloat(array $hits, string $fieldName): array + { + $returnPrices = []; + foreach ($hits as $hit) { + $returnPrices[] = (float)$hit['fields'][$fieldName][0]; + } + + return $returnPrices; + } + + /** + * Prepare base query for search. + * + * @param array|null $from + * @param array|null $to + * @return array + */ + private function prepareBaseRequestQuery($from = null, $to = null): array + { + $requestQuery = [ + 'index' => $this->searchIndexNameResolver->getIndexName($this->storeId, Fulltext::INDEXER_ID), + 'type' => $this->clientConfig->getEntityType(), + 'body' => [ + 'stored_fields' => [ + '_id', + ], + 'query' => [ + 'bool' => [ + 'must' => [ + 'match_all' => new \stdClass(), + ], + 'filter' => [ + 'bool' => [ + 'must' => [ + [ + 'terms' => [ + '_id' => $this->entityIds, + ], + ], + [ + 'range' => [ + $this->fieldName => array_merge($from, $to), + ], + ], + ], + ], + ], + ], + ], + 'sort' => [ + $this->fieldName, + ], + ], + ]; + + return $requestQuery; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php index d0258286b6484..2a8ccdb5c72ac 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php @@ -42,7 +42,9 @@ public function __construct( } /** - * {@inheritdoc} + * @param string $attributeCode + * @param array $context + * @return string */ public function getFieldName($attributeCode, $context = []) { @@ -78,7 +80,8 @@ public function getFieldName($attributeCode, $context = []) } /** - * {@inheritdoc} + * @param array $context + * @return array */ public function getAllAttributesTypes($context = []) { @@ -115,4 +118,22 @@ public function getAllAttributesTypes($context = []) return $allAttributes; } + + /** + * @param string $frontendInput + * @param string $fieldType + * @param string $attributeCode + * @return string + */ + protected function getRefinedFieldName($frontendInput, $fieldType, $attributeCode) + { + switch ($frontendInput) { + case 'select': + return in_array($fieldType, ['string','integer'], true) ? $attributeCode . '_value' : $attributeCode; + case 'boolean': + return $fieldType === 'integer' ? $attributeCode . '_value' : $attributeCode; + default: + return $attributeCode; + } + } } diff --git a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php index b6ec06d48a487..44ab0dbc4d46c 100644 --- a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php @@ -15,21 +15,21 @@ class Elasticsearch implements ClientInterface { /** - * Elasticsearch Client instance + * Elasticsearch Client instances * - * @var \Elasticsearch\Client + * @var \Elasticsearch\Client[] */ - protected $client; + private $client; /** * @var array */ - protected $clientOptions; + private $clientOptions; /** * @var bool */ - protected $pingResult; + private $pingResult; /** * Initialize Elasticsearch Client @@ -53,10 +53,25 @@ public function __construct( $config = $this->buildConfig($options); $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true); } - $this->client = $elasticsearchClient; + $this->client[getmypid()] = $elasticsearchClient; $this->clientOptions = $options; } + /** + * Get Elasticsearch Client + * + * @return \Elasticsearch\Client + */ + private function getClient() + { + $pid = getmypid(); + if (!isset($this->client[$pid])) { + $config = $this->buildConfig($this->clientOptions); + $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true); + } + return $this->client[$pid]; + } + /** * Ping the Elasticsearch client * @@ -65,7 +80,7 @@ public function __construct( public function ping() { if ($this->pingResult === null) { - $this->pingResult = $this->client->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); + $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); } return $this->pingResult; } @@ -110,7 +125,7 @@ private function buildConfig($options = []) */ public function bulkQuery($query) { - $this->client->bulk($query); + $this->getClient()->bulk($query); } /** @@ -122,7 +137,7 @@ public function bulkQuery($query) */ public function createIndex($index, $settings) { - $this->client->indices()->create([ + $this->getClient()->indices()->create([ 'index' => $index, 'body' => $settings, ]); @@ -136,7 +151,7 @@ public function createIndex($index, $settings) */ public function deleteIndex($index) { - $this->client->indices()->delete(['index' => $index]); + $this->getClient()->indices()->delete(['index' => $index]); } /** @@ -147,7 +162,7 @@ public function deleteIndex($index) */ public function isEmptyIndex($index) { - $stats = $this->client->indices()->stats(['index' => $index, 'metric' => 'docs']); + $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) { return true; } @@ -172,7 +187,7 @@ public function updateAlias($alias, $newIndex, $oldIndex = '') $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; } - $this->client->indices()->updateAliases($params); + $this->getClient()->indices()->updateAliases($params); } /** @@ -183,7 +198,7 @@ public function updateAlias($alias, $newIndex, $oldIndex = '') */ public function indexExists($index) { - return $this->client->indices()->exists(['index' => $index]); + return $this->getClient()->indices()->exists(['index' => $index]); } /** @@ -198,7 +213,7 @@ public function existsAlias($alias, $index = '') if ($index) { $params['index'] = $index; } - return $this->client->indices()->existsAlias($params); + return $this->getClient()->indices()->existsAlias($params); } /** @@ -208,7 +223,7 @@ public function existsAlias($alias, $index = '') */ public function getAlias($alias) { - return $this->client->indices()->getAlias(['name' => $alias]); + return $this->getClient()->indices()->getAlias(['name' => $alias]); } /** @@ -267,7 +282,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) foreach ($fields as $field => $fieldInfo) { $params['body'][$entityType]['properties'][$field] = $fieldInfo; } - $this->client->indices()->putMapping($params); + $this->getClient()->indices()->putMapping($params); } /** @@ -279,7 +294,7 @@ public function addFieldsMapping(array $fields, $index, $entityType) */ public function deleteMapping($index, $entityType) { - $this->client->indices()->deleteMapping([ + $this->getClient()->indices()->deleteMapping([ 'index' => $index, 'type' => $entityType, ]); @@ -293,8 +308,7 @@ public function deleteMapping($index, $entityType) */ public function query($query) { - $params = array_merge($query, ['client' => ['timeout' => $this->clientOptions['timeout']]]); - return $this->client->search($params); + return $this->getClient()->search($query); } /** @@ -305,6 +319,6 @@ public function query($query) */ public function suggest($query) { - return $this->client->suggest($query); + return $this->getClient()->suggest($query); } } diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Index.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Index.php index 49489f1cdb43d..d75bb9c8ccd34 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Index.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Index.php @@ -5,6 +5,7 @@ */ namespace Magento\Elasticsearch\Model\ResourceModel; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Store\Model\StoreManagerInterface; use Magento\Catalog\Api\ProductRepositoryInterface; @@ -12,6 +13,7 @@ use Magento\Eav\Model\Config; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Search\Request\IndexScopeResolverInterface as TableResolver; /** * Elasticsearch index resource model @@ -47,6 +49,8 @@ class Index extends \Magento\AdvancedSearch\Model\ResourceModel\Index * @param CategoryRepositoryInterface $categoryRepository * @param Config $eavConfig * @param null $connectionName + * @param TableResolver|null $tableResolver + * @param DimensionCollectionFactory|null $dimensionCollectionFactory * @SuppressWarnings(Magento.TypeDuplication) */ public function __construct( @@ -56,7 +60,9 @@ public function __construct( ProductRepositoryInterface $productRepository, CategoryRepositoryInterface $categoryRepository, Config $eavConfig, - $connectionName = null + $connectionName = null, + TableResolver $tableResolver = null, + DimensionCollectionFactory $dimensionCollectionFactory = null ) { $this->productRepository = $productRepository; $this->categoryRepository = $categoryRepository; @@ -65,7 +71,9 @@ public function __construct( $context, $storeManager, $metadataPool, - $connectionName + $connectionName, + $tableResolver, + $dimensionCollectionFactory ); } diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/composer.json b/app/code/Magento/Elasticsearch/Test/Mftf/composer.json deleted file mode 100644 index 4f0b74e9c5660..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-elasticsearch", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-advanced-search": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-search": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-search": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapperTest.php index 2128ee57681a8..6ee32a58698c6 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapperTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapperTest.php @@ -180,7 +180,9 @@ public function testGetFieldNameWithoutAttribute() /** * @dataProvider attributeProvider * @param string $attributeCode - * + * @param string $inputType + * @param array $searchAttributes + * @param array $expected * @return void */ public function testGetAllAttributesTypes($attributeCode, $inputType, $searchAttributes, $expected) @@ -227,17 +229,17 @@ public function testGetAllAttributesTypes($attributeCode, $inputType, $searchAtt public function attributeCodeProvider() { return [ - ['id', 'id', 'string'], - ['status', 'status', 'string'], - ['status', 'status', 'string', ['type'=>'default']], - ['price', 'price_0_1', 'string', ['type'=>'default']], - ['position', 'position_category_1', 'string', ['type'=>'default']], - ['price', 'price_2_3', 'string', ['type'=>'default', 'customerGroupId'=>'2', 'websiteId'=>'3']], - ['position', 'position_category_3', 'string', ['type'=>'default', 'categoryId'=>'3']], - ['color', 'color', 'select', ['type'=>'default']], - ['description', 'sort_description', 'string', ['type'=>'some']], - ['*', '_all', 'string', ['type'=>'text']], - ['description', 'description', 'string', ['type'=>'text']], + ['id', 'id', 'text'], + ['status', 'status', 'text'], + ['status', 'status_value', 'text', ['type'=>'default']], + ['price', 'price_0_1', 'text', ['type'=>'default']], + ['position', 'position_category_1', 'text', ['type'=>'default']], + ['price', 'price_2_3', 'text', ['type'=>'default', 'customerGroupId'=>'2', 'websiteId'=>'3']], + ['position', 'position_category_3', 'text', ['type'=>'default', 'categoryId'=>'3']], + ['color', 'color_value', 'text', ['type'=>'text']], + ['description', 'sort_description', 'text', ['type'=>'some']], + ['*', '_all', 'text', ['type'=>'text']], + ['description', 'description_value', 'text', ['type'=>'text']], ]; } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php index 415c8b61ac6ca..026d385da34da 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php @@ -343,7 +343,7 @@ public function testAddFieldsMapping() 'product' => [ '_all' => [ 'enabled' => true, - 'type' => 'text' + 'type' => 'text', ], 'properties' => [ 'name' => [ @@ -356,7 +356,8 @@ public function testAddFieldsMapping() 'match' => 'price_*', 'match_mapping_type' => 'string', 'mapping' => [ - 'type' => 'float' + 'type' => 'float', + 'store' => true, ], ], ], @@ -366,7 +367,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', - 'index' => 'no' + 'index' => 'no', ], ], ], @@ -375,7 +376,7 @@ public function testAddFieldsMapping() 'match' => 'position_*', 'match_mapping_type' => 'string', 'mapping' => [ - 'type' => 'int' + 'type' => 'int', ], ], ], @@ -409,7 +410,7 @@ public function testAddFieldsMappingFailure() 'product' => [ '_all' => [ 'enabled' => true, - 'type' => 'text' + 'type' => 'text', ], 'properties' => [ 'name' => [ @@ -422,7 +423,8 @@ public function testAddFieldsMappingFailure() 'match' => 'price_*', 'match_mapping_type' => 'string', 'mapping' => [ - 'type' => 'float' + 'type' => 'float', + 'store' => true, ], ], ], @@ -441,7 +443,7 @@ public function testAddFieldsMappingFailure() 'match' => 'position_*', 'match_mapping_type' => 'string', 'mapping' => [ - 'type' => 'int' + 'type' => 'int', ], ], ], diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php new file mode 100644 index 0000000000000..4030d2dfaf0c5 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/SearchAdapter/Aggregation/IntervalTest.php @@ -0,0 +1,323 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Test\Unit\Elasticsearch5\SearchAdapter\Aggregation; + +use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; + +/** + * Test for Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval class. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class IntervalTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Interval + */ + private $model; + + /** + * @var ConnectionManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionManager; + + /** + * @var FieldMapperInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $fieldMapper; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $clientConfig; + + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManager; + + /** + * @var CustomerSession|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerSession; + + /** + * @var ElasticsearchClient|\PHPUnit_Framework_MockObject_MockObject + */ + private $clientMock; + + /** + * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var SearchIndexNameResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $searchIndexNameResolver; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->connectionManager = $this->getMockBuilder(ConnectionManager::class) + ->setMethods(['getConnection']) + ->disableOriginalConstructor() + ->getMock(); + $this->fieldMapper = $this->getMockBuilder(FieldMapperInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->clientConfig = $this->getMockBuilder(Config::class) + ->setMethods([ + 'getIndexName', + 'getEntityType', + ]) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerSession = $this->getMockBuilder(CustomerSession::class) + ->setMethods(['getCustomerGroupId']) + ->disableOriginalConstructor() + ->getMock(); + $this->customerSession->expects($this->any()) + ->method('getCustomerGroupId') + ->willReturn(1); + $this->storeMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->searchIndexNameResolver = $this + ->getMockBuilder(SearchIndexNameResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeMock->expects($this->any()) + ->method('getWebsiteId') + ->willReturn(1); + $this->storeMock->expects($this->any()) + ->method('getId') + ->willReturn(1); + $this->clientConfig->expects($this->any()) + ->method('getIndexName') + ->willReturn('indexName'); + $this->clientConfig->expects($this->any()) + ->method('getEntityType') + ->willReturn('product'); + $this->clientMock = $this->getMockBuilder(ElasticsearchClient::class) + ->setMethods(['query']) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionManager->expects($this->any()) + ->method('getConnection') + ->willReturn($this->clientMock); + + $objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $objectManagerHelper->getObject( + Interval::class, + [ + 'connectionManager' => $this->connectionManager, + 'fieldMapper' => $this->fieldMapper, + 'clientConfig' => $this->clientConfig, + 'searchIndexNameResolver' => $this->searchIndexNameResolver, + 'fieldName' => 'price_0_1', + 'storeId' => 1, + 'entityIds' => [265, 313, 281], + ] + ); + } + + /** + * @dataProvider loadParamsProvider + * @param string $limit + * @param string $offset + * @param string $lower + * @param string $upper + * @param array $queryResult + * @param array $expected + * @return void + */ + public function testLoad( + string $limit, + string $offset, + string $lower, + string $upper, + array $queryResult, + array $expected + ): void { + $this->processQuery($queryResult); + + $this->assertEquals( + $expected, + $this->model->load($limit, $offset, $lower, $upper) + ); + } + + /** + * @dataProvider loadPrevParamsProvider + * @param string $data + * @param string $index + * @param string $lower + * @param array $queryResult + * @param array|bool $expected + * @return void + */ + public function testLoadPrev(string $data, string $index, string $lower, array $queryResult, $expected): void + { + $this->processQuery($queryResult); + + $this->assertEquals( + $expected, + $this->model->loadPrevious($data, $index, $lower) + ); + } + + /** + * @dataProvider loadNextParamsProvider + * @param string $data + * @param string $rightIndex + * @param string $upper + * @param array $queryResult + * @param array|bool $expected + * @return void + */ + public function testLoadNext(string $data, string $rightIndex, string $upper, array $queryResult, $expected): void + { + $this->processQuery($queryResult); + + $this->assertEquals( + $expected, + $this->model->loadNext($data, $rightIndex, $upper) + ); + } + + /** + * @param array $queryResult + * @return void + */ + private function processQuery(array $queryResult): void + { + $this->searchIndexNameResolver->expects($this->any()) + ->method('getIndexName') + ->willReturn('magento2_product_1'); + $this->clientConfig->expects($this->any()) + ->method('getEntityType') + ->willReturn('document'); + $this->clientMock->expects($this->any()) + ->method('query') + ->willReturn($queryResult); + } + + /** + * @return array + */ + public function loadParamsProvider(): array + { + return [ + [ + 'limit' => '6', + 'offset' => '2', + 'lower' => '24', + 'upper' => '42', + 'queryResult' => [ + 'hits' => [ + 'hits' => [ + [ + 'fields' => [ + 'price_0_1' => [25], + ], + ], + ], + ], + ], + 'expected' => [25], + ], + ]; + } + + /** + * @return array + */ + public function loadPrevParamsProvider(): array + { + return [ + [ + 'data' => '24', + 'rightIndex' => '1', + 'upper' => '24', + 'queryResult' => [ + 'hits' => [ + 'total'=> '1', + 'hits' => [ + [ + 'fields' => [ + 'price_0_1' => ['25'], + ], + ], + ], + ], + ], + 'expected' => ['25.0'], + ], + [ + 'data' => '24', + 'rightIndex' => '1', + 'upper' => '24', + 'queryResult' => [ + 'hits' => ['total'=> '0'], + ], + 'expected' => false, + ], + ]; + } + + /** + * @return array + */ + public function loadNextParamsProvider(): array + { + return [ + [ + 'data' => '24', + 'rightIndex' => '2', + 'upper' => '42', + 'queryResult' => [ + 'hits' => [ + 'total'=> '1', + 'hits' => [ + [ + 'fields' => [ + 'price_0_1' => ['25'], + ], + ], + ], + ], + ], + 'expected' => ['25.0'], + ], + [ + 'data' => '24', + 'rightIndex' => '2', + 'upper' => '42', + 'queryResult' => [ + 'hits' => ['total'=> '0'], + ], + 'expected' => false, + ], + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/ProductFieldMapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/ProductFieldMapperTest.php new file mode 100644 index 0000000000000..8b7ac6abbb190 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/ProductFieldMapperTest.php @@ -0,0 +1,298 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\FieldMapper; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Elasticsearch\Model\Adapter\FieldType; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class ProductFieldMapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper + */ + protected $mapper; + + /** + * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + protected $eavConfig; + + /** + * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + */ + protected $coreRegistry; + + /** + * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject + */ + protected $customerSession; + + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $storeManager; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute|\PHPUnit_Framework_MockObject_MockObject + */ + protected $eavAttributeResource; + + /** + * @var FieldType|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fieldType; + + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $store; + + /** + * Set up test environment + * + * @return void + */ + protected function setUp() + { + $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) + ->disableOriginalConstructor() + ->setMethods(['getEntityType', 'getAttribute', 'getEntityAttributeCodes']) + ->getMock(); + + $this->fieldType = $this->getMockBuilder(FieldType::class) + ->disableOriginalConstructor() + ->setMethods(['getFieldType']) + ->getMock(); + + $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerGroupId']) + ->getMock(); + + $this->storeManager = $this->storeManager = $this->getMockForAbstractClass( + \Magento\Store\Model\StoreManagerInterface::class, + [], + '', + false + ); + + $this->store = $this->getMockForAbstractClass( + \Magento\Store\Api\Data\StoreInterface::class, + [], + '', + false, + false, + true, + ['getWebsiteId', 'getRootCategoryId'] + ); + + $this->coreRegistry = $this->createMock(\Magento\Framework\Registry::class); + + $objectManager = new ObjectManagerHelper($this); + + $this->eavAttributeResource = $this->createPartialMock( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, + [ + '__wakeup', + 'getBackendType', + 'getFrontendInput' + ] + ); + + $this->mapper = $objectManager->getObject( + \Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper::class, + [ + 'eavConfig' => $this->eavConfig, + 'storeManager' => $this->storeManager, + 'fieldType' => $this->fieldType, + 'customerSession' => $this->customerSession, + 'coreRegistry' => $this->coreRegistry + ] + ); + } + + /** + * @dataProvider attributeCodeProvider + * @param string $attributeCode + * @param string $fieldName + * @param string $fieldType + * @param array $context + * + * @return void + */ + public function testGetFieldName($attributeCode, $fieldName, $fieldType, $context = []) + { + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->setMethods(['getBackendType', 'getFrontendInput', 'getAttribute']) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerSession->expects($this->any()) + ->method('getCustomerGroupId') + ->willReturn('0'); + + $this->storeManager->expects($this->any()) + ->method('getStore') + ->willReturn($this->store); + $this->store->expects($this->any()) + ->method('getWebsiteId') + ->willReturn('1'); + $this->store->expects($this->any()) + ->method('getRootCategoryId') + ->willReturn('1'); + + $this->eavConfig->expects($this->any())->method('getAttribute') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode) + ->willReturn($attributeMock); + + $attributeMock->expects($this->any())->method('getFrontendInput') + ->will($this->returnValue('select')); + + $this->fieldType->expects($this->any())->method('getFieldType') + ->with($attributeMock) + ->willReturn($fieldType); + + $this->assertEquals( + $fieldName, + $this->mapper->getFieldName($attributeCode, $context) + ); + } + + /** + * @return void + */ + public function testGetFieldNameWithoutAttribute() + { + $this->eavConfig->expects($this->any())->method('getAttribute') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, 'attr1') + ->willReturn(''); + + $this->assertEquals( + 'attr1', + $this->mapper->getFieldName('attr1', []) + ); + } + + /** + * @dataProvider attributeProvider + * @param string $attributeCode + * @param string $inputType + * @param array $searchAttributes + * @param array $expected + * @return void + */ + public function testGetAllAttributesTypes($attributeCode, $inputType, $searchAttributes, $expected) + { + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->eavConfig->expects($this->any())->method('getEntityAttributeCodes') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE) + ->willReturn([$attributeCode]); + + $this->eavConfig->expects($this->any())->method('getAttribute') + ->with(ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode) + ->willReturn($attributeMock); + + $this->fieldType->expects($this->once())->method('getFieldType')->willReturn(FieldType::ES_DATA_TYPE_INT); + + $attributeMock->expects($this->any()) + ->method('getIsSearchable') + ->willReturn($searchAttributes['searchable']); + $attributeMock->expects($this->any()) + ->method('getIsFilterable') + ->willReturn($searchAttributes['filterable']); + $attributeMock->expects($this->any()) + ->method('getIsFilterableInSearch') + ->willReturn($searchAttributes['filterableInSearch']); + $attributeMock->expects($this->any()) + ->method('getIsVisibleInAdvancedSearch') + ->willReturn($searchAttributes['advSearch']); + + $attributeMock->expects($this->any())->method('getFrontendInput') + ->will($this->returnValue($inputType)); + + $this->assertEquals( + $expected, + $this->mapper->getAllAttributesTypes() + ); + } + + /** + * @return array + */ + public function attributeCodeProvider() + { + return [ + ['id', 'id', 'string'], + ['status', 'status', 'string'], + ['status', 'status_value', 'string', ['type'=>'default']], + ['price', 'price_0_1', 'string', ['type'=>'default']], + ['position', 'position_category_1', 'string', ['type'=>'default']], + ['price', 'price_2_3', 'string', ['type'=>'default', 'customerGroupId'=>'2', 'websiteId'=>'3']], + ['position', 'position_category_3', 'string', ['type'=>'default', 'categoryId'=>'3']], + ['color', 'color_value', 'string', ['type'=>'text']], + ['description', 'sort_description', 'string', ['type'=>'some']], + ['*', '_all', 'string', ['type'=>'text']], + ['description', 'description_value', 'string', ['type'=>'text']], + ]; + } + + /** + * @return array + */ + public function attributeProvider() + { + return [ + [ + 'category_ids', + 'text', + ['searchable' => false, 'filterable' => false, 'filterableInSearch' => false, 'advSearch' => false], + ['category_ids' => ['type' => 'integer']] + ], + [ + 'attr_code', + 'string', + ['searchable' => false, 'filterable' => false, 'filterableInSearch' => false, 'advSearch' => false], + ['attr_code' => ['type' => 'integer', 'index' => 'no']] + ], + [ + 'attr_code', + 'string', + ['searchable' => '0', 'filterable' => '0', 'filterableInSearch' => '0', 'advSearch' => '0'], + ['attr_code' => ['type' => 'integer', 'index' => 'no']] + ], + [ + 'attr_code', + 'string', + ['searchable' => true, 'filterable' => false, 'filterableInSearch' => false, 'advSearch' => false], + ['attr_code' => ['type' => 'integer']] + ], + [ + 'attr_code', + 'string', + ['searchable' => '1', 'filterable' => '0', 'filterableInSearch' => '0', 'advSearch' => '0'], + ['attr_code' => ['type' => 'integer']] + ], + [ + 'attr_code', + 'string', + ['searchable' => false, 'filterable' => false, 'filterableInSearch' => false, 'advSearch' => true], + ['attr_code' => ['type' => 'integer']] + ], + [ + 'attr_code', + 'string', + ['searchable' => '0', 'filterable' => '0', 'filterableInSearch' => '1', 'advSearch' => '0'], + ['attr_code' => ['type' => 'integer']] + ], + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php index c11fb64cde7e6..bac2b7ea28908 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/IndexTest.php @@ -239,6 +239,20 @@ protected function setUp() ] ); + $traversableMock = $this->createMock(\Traversable::class); + $dimensionsMock = $this->createMock(\Magento\Framework\Indexer\MultiDimensionProvider::class); + $dimensionsMock->method('getIterator')->willReturn($traversableMock); + + $indexScopeResolverMock = $this->createMock( + \Magento\Framework\Search\Request\IndexScopeResolverInterface::class + ); + + $dimensionFactoryMock = $this->createMock( + \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + ); + $dimensionFactoryMock->method('create')->willReturn($dimensionsMock); + $indexScopeResolverMock->method('resolve')->willReturn('catalog_product_index_price'); + $this->model = $objectManager->getObject( \Magento\Elasticsearch\Model\ResourceModel\Index::class, [ @@ -249,7 +263,8 @@ protected function setUp() 'categoryRepository' => $this->categoryRepository, 'eavConfig' => $this->eavConfig, 'connectionName' => 'default', - 'tableResolver' => $this->tableResolver + 'tableResolver' => $this->tableResolver, + 'dimensionCollectionFactory' => $dimensionFactoryMock, ] ); } diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json index a6b4f93ade579..a821506f5ef6e 100644 --- a/app/code/Magento/Elasticsearch/composer.json +++ b/app/code/Magento/Elasticsearch/composer.json @@ -19,7 +19,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 2d569eecfee58..0cfaba845fd6a 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -166,7 +166,7 @@ <arguments> <argument name="intervals" xsi:type="array"> <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Aggregation\Interval</item> - <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Aggregation\Interval</item> + <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Elasticsearch/etc/esconfig.xml b/app/code/Magento/Elasticsearch/etc/esconfig.xml index 0a87b58fd3a18..49124c4b70392 100644 --- a/app/code/Magento/Elasticsearch/etc/esconfig.xml +++ b/app/code/Magento/Elasticsearch/etc/esconfig.xml @@ -16,7 +16,6 @@ <fr_FR>french</fr_FR> <nl_NL>dutch</nl_NL> <pt_BR>portuguese</pt_BR> - <zh_Hans_CN>cjk</zh_Hans_CN> </stemmer> <stopwords_file> <default>stopwords.csv</default> diff --git a/app/code/Magento/Elasticsearch/etc/indexer.xml b/app/code/Magento/Elasticsearch/etc/indexer.xml index 245829396a67b..8d75a59f84048 100644 --- a/app/code/Magento/Elasticsearch/etc/indexer.xml +++ b/app/code/Magento/Elasticsearch/etc/indexer.xml @@ -9,6 +9,7 @@ <indexer id="catalogsearch_fulltext"> <dependencies> <indexer id="cataloginventory_stock" /> + <indexer id="catalog_product_price" /> </dependencies> </indexer> </config> diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Delete.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Delete.php index 9f248f2cebc3d..eedcf5009ccfa 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Delete.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Delete.php @@ -22,21 +22,21 @@ public function execute() if (count($template->getSystemConfigPathsWhereCurrentlyUsed()) == 0) { $template->delete(); // display success message - $this->messageManager->addSuccess(__('You deleted the email template.')); + $this->messageManager->addSuccessMessage(__('You deleted the email template.')); $this->_objectManager->get(\Magento\Framework\App\ReinitableConfig::class)->reinit(); // go to grid $this->_redirect('adminhtml/*/'); return; } // display error message - $this->messageManager->addError(__('The email template is currently being used.')); + $this->messageManager->addErrorMessage(__('The email template is currently being used.')); // redirect to edit form $this->_redirect('adminhtml/*/edit', ['id' => $template->getId()]); return; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('We can\'t delete email template data right now. Please review log and try again.') ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); @@ -52,7 +52,7 @@ public function execute() } } // display error message - $this->messageManager->addError(__('We can\'t find an email template to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find an email template to delete.')); // go to grid $this->_redirect('adminhtml/*/'); } diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php index e23cd6cf17eb2..404f97c937167 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Preview.php @@ -21,7 +21,9 @@ public function execute() $this->_view->renderLayout(); $this->getResponse()->setHeader('Content-Security-Policy', "script-src 'none'"); } catch (\Exception $e) { - $this->messageManager->addError(__('An error occurred. The email template can not be opened for preview.')); + $this->messageManager->addErrorMessage( + __('An error occurred. The email template can not be opened for preview.') + ); $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Save.php b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Save.php index cf31259b7885d..135506f4068a8 100644 --- a/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Save.php +++ b/app/code/Magento/Email/Controller/Adminhtml/Email/Template/Save.php @@ -22,7 +22,7 @@ public function execute() $template = $this->_initTemplate('id'); if (!$template->getId() && $id) { - $this->messageManager->addError(__('This email template no longer exists.')); + $this->messageManager->addErrorMessage(__('This email template no longer exists.')); $this->_redirect('adminhtml/*/'); return; } @@ -55,7 +55,7 @@ public function execute() $template->save(); $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData(false); - $this->messageManager->addSuccess(__('You saved the email template.')); + $this->messageManager->addSuccessMessage(__('You saved the email template.')); $this->_redirect('adminhtml/*'); } catch (\Exception $e) { $this->_objectManager->get( @@ -64,7 +64,7 @@ public function execute() 'email_template_form_data', $request->getParams() ); - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_forward('new'); } } diff --git a/app/code/Magento/Email/Test/Mftf/composer.json b/app/code/Magento/Email/Test/Mftf/composer.json deleted file mode 100644 index 9f9b087c5fc15..0000000000000 --- a/app/code/Magento/Email/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-email", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Email/etc/db_schema_whitelist.json b/app/code/Magento/Email/etc/db_schema_whitelist.json index 2ad9cddc70f96..471a51771b854 100644 --- a/app/code/Magento/Email/etc/db_schema_whitelist.json +++ b/app/code/Magento/Email/etc/db_schema_whitelist.json @@ -1,26 +1,26 @@ { - "email_template": { - "column": { - "template_id": true, - "template_code": true, - "template_text": true, - "template_styles": true, - "template_type": true, - "template_subject": true, - "template_sender_name": true, - "template_sender_email": true, - "added_at": true, - "modified_at": true, - "orig_template_code": true, - "orig_template_variables": true - }, - "index": { - "EMAIL_TEMPLATE_ADDED_AT": true, - "EMAIL_TEMPLATE_MODIFIED_AT": true - }, - "constraint": { - "PRIMARY": true, - "EMAIL_TEMPLATE_TEMPLATE_CODE": true + "email_template": { + "column": { + "template_id": true, + "template_code": true, + "template_text": true, + "template_styles": true, + "template_type": true, + "template_subject": true, + "template_sender_name": true, + "template_sender_email": true, + "added_at": true, + "modified_at": true, + "orig_template_code": true, + "orig_template_variables": true + }, + "index": { + "EMAIL_TEMPLATE_ADDED_AT": true, + "EMAIL_TEMPLATE_MODIFIED_AT": true + }, + "constraint": { + "PRIMARY": true, + "EMAIL_TEMPLATE_TEMPLATE_CODE": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php new file mode 100644 index 0000000000000..aae30026b2b77 --- /dev/null +++ b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\EncryptionKey\Setup\Patch\Data; + +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Migrate encrypted configuration values to the latest cipher + */ +class SodiumChachaPatch implements DataPatchInterface +{ + /** + * @var \Magento\Framework\Setup\ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var \Magento\Config\Model\Config\Structure + */ + private $structure; + + /** + * @var \Magento\Framework\Encryption\EncryptorInterface + */ + private $encryptor; + + /** + * @var \Magento\Framework\App\State + */ + private $state; + + /** + * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + * @param \Magento\Config\Model\Config\Structure\Proxy $structure + * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor + * @param \Magento\Framework\App\State $state + */ + public function __construct( + \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup, + \Magento\Config\Model\Config\Structure\Proxy $structure, + \Magento\Framework\Encryption\EncryptorInterface $encryptor, + \Magento\Framework\App\State $state + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->structure = $structure; + $this->encryptor = $encryptor; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function apply() + { + $this->moduleDataSetup->startSetup(); + + $this->reEncryptSystemConfigurationValues(); + + $this->moduleDataSetup->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } + + private function reEncryptSystemConfigurationValues() + { + $structure = $this->structure; + $paths = $this->state->emulateAreaCode( + \Magento\Framework\App\Area::AREA_ADMINHTML, + function () use ($structure) { + return $structure->getFieldPathsByAttribute( + 'backend_model', + \Magento\Config\Model\Config\Backend\Encrypted::class + ); + } + ); + // walk through found data and re-encrypt it + if ($paths) { + $table = $this->moduleDataSetup->getTable('core_config_data'); + $values = $this->moduleDataSetup->getConnection()->fetchPairs( + $this->moduleDataSetup->getConnection() + ->select() + ->from($table, ['config_id', 'value']) + ->where('path IN (?)', $paths) + ->where('value NOT LIKE ?', '') + ); + foreach ($values as $configId => $value) { + $this->moduleDataSetup->getConnection()->update( + $table, + ['value' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], + ['config_id = ?' => (int)$configId] + ); + } + } + } +} diff --git a/app/code/Magento/EncryptionKey/Test/Mftf/composer.json b/app/code/Magento/EncryptionKey/Test/Mftf/composer.json deleted file mode 100644 index 732a18753779f..0000000000000 --- a/app/code/Magento/EncryptionKey/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-encryption-key", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json index 0bf0728606684..4a140c4b1315e 100644 --- a/app/code/Magento/EncryptionKey/composer.json +++ b/app/code/Magento/EncryptionKey/composer.json @@ -12,7 +12,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Fedex/Model/Carrier.php b/app/code/Magento/Fedex/Model/Carrier.php index 8841cdd4f9a8b..f6e63e04ac559 100644 --- a/app/code/Magento/Fedex/Model/Carrier.php +++ b/app/code/Magento/Fedex/Model/Carrier.php @@ -7,8 +7,10 @@ namespace Magento\Fedex\Model; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; use Magento\Framework\Module\Dir; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Webapi\Soap\ClientFactory; use Magento\Framework\Xml\Security; use Magento\Quote\Model\Quote\Address\RateRequest; use Magento\Shipping\Model\Carrier\AbstractCarrierOnline; @@ -17,7 +19,6 @@ /** * Fedex shipping implementation * - * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -143,6 +144,11 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C */ private $serializer; + /** + * @var ClientFactory + */ + private $soapClientFactory; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory @@ -164,7 +170,7 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory * @param array $data * @param Json|null $serializer - * + * @param ClientFactory|null $soapClientFactory * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -187,7 +193,8 @@ public function __construct( \Magento\Framework\Module\Dir\Reader $configReader, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, array $data = [], - Json $serializer = null + Json $serializer = null, + ClientFactory $soapClientFactory = null ) { $this->_storeManager = $storeManager; $this->_productCollectionFactory = $productCollectionFactory; @@ -214,6 +221,7 @@ public function __construct( $this->_rateServiceWsdl = $wsdlBasePath . 'RateService_v10.wsdl'; $this->_trackServiceWsdl = $wsdlBasePath . 'TrackService_v' . self::$trackServiceVersion . '.wsdl'; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->soapClientFactory = $soapClientFactory ?: ObjectManager::getInstance()->get(ClientFactory::class); } /** @@ -225,7 +233,7 @@ public function __construct( */ protected function _createSoapClient($wsdl, $trace = false) { - $client = new \SoapClient($wsdl, ['trace' => $trace]); + $client = $this->soapClientFactory->create($wsdl, ['trace' => $trace]); $client->__setLocation( $this->getConfigFlag( 'sandbox_mode' @@ -1263,7 +1271,7 @@ protected function _formShipmentRequest(\Magento\Framework\DataObject $request) $countriesOfManufacture[] = $product->getCountryOfManufacture(); } - $paymentType = $request->getIsReturn() ? 'RECIPIENT' : 'SENDER'; + $paymentType = $this->getPaymentType($request); $optionType = $request->getShippingMethod() == self::RATE_REQUEST_SMARTPOST ? 'SERVICE_DEFAULT' : $packageParams->getDeliveryConfirmation(); $requestClient = [ @@ -1749,4 +1757,18 @@ private function parseDate($timestamp) return false; } + + /** + * Defines payment type by request. + * Two values are available: RECIPIENT or SENDER. + * + * @param DataObject $request + * @return string + */ + private function getPaymentType(DataObject $request): string + { + return $request->getIsReturn() && $request->getShippingMethod() !== self::RATE_REQUEST_SMARTPOST + ? 'RECIPIENT' + : 'SENDER'; + } } diff --git a/app/code/Magento/Fedex/Test/Mftf/composer.json b/app/code/Magento/Fedex/Test/Mftf/composer.json deleted file mode 100644 index 6152c679ec796..0000000000000 --- a/app/code/Magento/Fedex/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-fedex", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GiftMessage/Model/Plugin/MergeQuoteItems.php b/app/code/Magento/GiftMessage/Model/Plugin/MergeQuoteItems.php new file mode 100644 index 0000000000000..2c097cc9a6653 --- /dev/null +++ b/app/code/Magento/GiftMessage/Model/Plugin/MergeQuoteItems.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GiftMessage\Model\Plugin; + +use Magento\Quote\Model\Quote\Item; +use Magento\Quote\Model\Quote\Item\Processor; + +class MergeQuoteItems +{ + /** + * Resolves gift message to be + * applied to merged quote items. + * + * @param Processor $subject + * @param Item $result + * @param Item $source + * @return Item + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterMerge(Processor $subject, Item $result, Item $source): Item + { + $giftMessageId = $source->getGiftMessageId(); + + if ($giftMessageId) { + $result->setGiftMessageId($giftMessageId); + } + + return $result; + } +} diff --git a/app/code/Magento/GiftMessage/Observer/SalesEventQuoteMerge.php b/app/code/Magento/GiftMessage/Observer/SalesEventQuoteMerge.php new file mode 100644 index 0000000000000..044a0bf91c982 --- /dev/null +++ b/app/code/Magento/GiftMessage/Observer/SalesEventQuoteMerge.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GiftMessage\Observer; + +use Magento\Framework\Event\ObserverInterface; +use Magento\Quote\Model\Quote; + +/** + * Gift Message Observer Model + */ +class SalesEventQuoteMerge implements ObserverInterface +{ + /** + * Sets gift message to customer quote from guest quote. + * + * @param \Magento\Framework\Event\Observer $observer + * @return $this + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + /** @var Quote $targetQuote */ + $targetQuote = $observer->getData('quote'); + /** @var Quote $sourceQuote */ + $sourceQuote = $observer->getData('source'); + + $giftMessageId = $sourceQuote->getGiftMessageId(); + if ($giftMessageId) { + $targetQuote->setGiftMessageId($giftMessageId); + } + + return $this; + } +} diff --git a/app/code/Magento/GiftMessage/Test/Mftf/composer.json b/app/code/Magento/GiftMessage/Test/Mftf/composer.json deleted file mode 100644 index 06f8c507d25c9..0000000000000 --- a/app/code/Magento/GiftMessage/Test/Mftf/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "magento/functional-test-module-gift-message", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-multishipping": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GiftMessage/etc/db_schema_whitelist.json b/app/code/Magento/GiftMessage/etc/db_schema_whitelist.json index 959049be1930d..e3fed1cadd1da 100644 --- a/app/code/Magento/GiftMessage/etc/db_schema_whitelist.json +++ b/app/code/Magento/GiftMessage/etc/db_schema_whitelist.json @@ -1,45 +1,45 @@ { - "gift_message": { - "column": { - "gift_message_id": true, - "customer_id": true, - "sender": true, - "recipient": true, - "message": true + "gift_message": { + "column": { + "gift_message_id": true, + "customer_id": true, + "sender": true, + "recipient": true, + "message": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "quote": { - "column": { - "gift_message_id": true - } - }, - "quote_address": { - "column": { - "gift_message_id": true - } - }, - "quote_item": { - "column": { - "gift_message_id": true - } - }, - "quote_address_item": { - "column": { - "gift_message_id": true - } - }, - "sales_order": { - "column": { - "gift_message_id": true - } - }, - "sales_order_item": { - "column": { - "gift_message_id": true, - "gift_message_available": true + "quote": { + "column": { + "gift_message_id": true + } + }, + "quote_address": { + "column": { + "gift_message_id": true + } + }, + "quote_item": { + "column": { + "gift_message_id": true + } + }, + "quote_address_item": { + "column": { + "gift_message_id": true + } + }, + "sales_order": { + "column": { + "gift_message_id": true + } + }, + "sales_order_item": { + "column": { + "gift_message_id": true, + "gift_message_available": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/GiftMessage/etc/frontend/di.xml b/app/code/Magento/GiftMessage/etc/frontend/di.xml index 1566c51ee9df3..a4837e0180c0b 100644 --- a/app/code/Magento/GiftMessage/etc/frontend/di.xml +++ b/app/code/Magento/GiftMessage/etc/frontend/di.xml @@ -43,4 +43,7 @@ </argument> </arguments> </type> + <type name="Magento\Quote\Model\Quote\Item\Processor"> + <plugin name="mergeQuoteItems" type="Magento\GiftMessage\Model\Plugin\MergeQuoteItems"/> + </type> </config> diff --git a/app/code/Magento/GiftMessage/etc/frontend/events.xml b/app/code/Magento/GiftMessage/etc/frontend/events.xml index 3af69730838ad..c786373b2094f 100644 --- a/app/code/Magento/GiftMessage/etc/frontend/events.xml +++ b/app/code/Magento/GiftMessage/etc/frontend/events.xml @@ -12,6 +12,9 @@ <event name="sales_convert_order_to_quote"> <observer name="giftmessage" instance="Magento\GiftMessage\Observer\SalesEventOrderToQuoteObserver" shared="false" /> </event> + <event name="sales_quote_merge_after"> + <observer name="giftmessage" instance="Magento\GiftMessage\Observer\SalesEventQuoteMerge" shared="false" /> + </event> <event name="checkout_type_multishipping_create_orders_single"> <observer name="giftmessage" instance="Magento\GiftMessage\Observer\MultishippingEventCreateOrdersObserver" shared="false" /> </event> diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json b/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json deleted file mode 100644 index d40fb323175fe..0000000000000 --- a/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-google-adwords", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json b/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 3413ffcac6e42..0000000000000 --- a/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-google-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleAnalytics/Test/Unit/Observer/SetGoogleAnalyticsOnOrderSuccessPageViewObserverTest.php b/app/code/Magento/GoogleAnalytics/Test/Unit/Observer/SetGoogleAnalyticsOnOrderSuccessPageViewObserverTest.php new file mode 100644 index 0000000000000..a883b0dab8c3b --- /dev/null +++ b/app/code/Magento/GoogleAnalytics/Test/Unit/Observer/SetGoogleAnalyticsOnOrderSuccessPageViewObserverTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GoogleAnalytics\Test\Unit\Observer; + +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\AbstractBlock; +use Magento\Framework\View\LayoutInterface; +use Magento\GoogleAnalytics\Helper\Data as GaDataHelper; +use Magento\GoogleAnalytics\Observer\SetGoogleAnalyticsOnOrderSuccessPageViewObserver; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; + +class SetGoogleAnalyticsOnOrderSuccessPageViewObserverTest extends TestCase +{ + /** + * @var Event|\PHPUnit_Framework_MockObject_MockObject + */ + private $eventMock; + + /** + * @var Observer|\PHPUnit_Framework_MockObject_MockObject + */ + private $observerMock; + + /** + * @var GaDataHelper|\PHPUnit_Framework_MockObject_MockObject + */ + private $googleAnalyticsDataMock; + + /** + * @var LayoutInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $layoutMock; + + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var SetGoogleAnalyticsOnOrderSuccessPageViewObserver + */ + private $orderSuccessObserver; + + /** + * Test setUp + */ + protected function setUp() + { + $this->googleAnalyticsDataMock = $this->getMockBuilder(GaDataHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->layoutMock = $this->getMockBuilder(LayoutInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->observerMock = $this->getMockBuilder(Observer::class)->getMock(); + $this->eventMock = $this->getMockBuilder(Event::class)->getMock(); + + $objectManager = new ObjectManager($this); + + $this->orderSuccessObserver = $objectManager->getObject( + SetGoogleAnalyticsOnOrderSuccessPageViewObserver::class, + [ + 'storeManager' => $this->storeManagerMock, + 'layout' => $this->layoutMock, + 'googleAnalyticsData' => $this->googleAnalyticsDataMock + ] + ); + } + + /** + * Observer test + */ + public function testExecuteWithNoOrderIds() + { + $this->observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($this->eventMock); + $this->eventMock->expects($this->once()) + ->method('__call') + ->with( + $this->equalTo('getOrderIds') + ) + ->willReturn([]); + $this->layoutMock->expects($this->never()) + ->method('getBlock'); + + $this->orderSuccessObserver->execute($this->observerMock); + } + + /** + * Observer test + */ + public function testExecuteWithOrderIds() + { + $blockMock = $this->getMockBuilder(AbstractBlock::class) + ->disableOriginalConstructor() + ->getMock(); + $orderIds = [8]; + + $this->observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($this->eventMock); + $this->eventMock->expects($this->once()) + ->method('__call') + ->with( + $this->equalTo('getOrderIds') + ) + ->willReturn($orderIds); + $this->layoutMock->expects($this->once()) + ->method('getBlock') + ->willReturn($blockMock); + $blockMock->expects($this->once()) + ->method('__call') + ->with( + $this->equalTo('setOrderIds'), + $this->equalTo([$orderIds]) + ); + + $this->orderSuccessObserver->execute($this->observerMock); + } +} diff --git a/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json b/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json deleted file mode 100644 index 2378140ac272a..0000000000000 --- a/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-google-optimizer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-google-analytics": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleOptimizer/etc/adminhtml/system.xml b/app/code/Magento/GoogleOptimizer/etc/adminhtml/system.xml index f0c703b7693c7..3c91c30c5cfa6 100644 --- a/app/code/Magento/GoogleOptimizer/etc/adminhtml/system.xml +++ b/app/code/Magento/GoogleOptimizer/etc/adminhtml/system.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="google" translate="label"> + <section id="google"> <group id="analytics"> <field id="experiments" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Enable Content Experiments</label> diff --git a/app/code/Magento/GoogleOptimizer/etc/db_schema_whitelist.json b/app/code/Magento/GoogleOptimizer/etc/db_schema_whitelist.json index e112a591cd1c8..20d2d1404a047 100644 --- a/app/code/Magento/GoogleOptimizer/etc/db_schema_whitelist.json +++ b/app/code/Magento/GoogleOptimizer/etc/db_schema_whitelist.json @@ -1,16 +1,16 @@ { - "googleoptimizer_code": { - "column": { - "code_id": true, - "entity_id": true, - "entity_type": true, - "store_id": true, - "experiment_script": true - }, - "constraint": { - "PRIMARY": true, - "GOOGLEOPTIMIZER_CODE_STORE_ID_STORE_STORE_ID": true, - "GOOGLEOPTIMIZER_CODE_STORE_ID_ENTITY_ID_ENTITY_TYPE": true + "googleoptimizer_code": { + "column": { + "code_id": true, + "entity_id": true, + "entity_type": true, + "store_id": true, + "experiment_script": true + }, + "constraint": { + "PRIMARY": true, + "GOOGLEOPTIMIZER_CODE_STORE_ID_STORE_STORE_ID": true, + "GOOGLEOPTIMIZER_CODE_STORE_ID_ENTITY_ID_ENTITY_TYPE": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/GraphQl/Test/Mftf/composer.json b/app/code/Magento/GraphQl/Test/Mftf/composer.json deleted file mode 100644 index e86f458e9e6a4..0000000000000 --- a/app/code/Magento/GraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 37ca2d8d7b378..c6651cdde0cb3 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -4,6 +4,10 @@ type Query { } +type Mutation { + placeholderMutation: String @doc(description: "Mutation type cannot be declared without fields. The placeholder will be removed when at least one mutation field is declared.") +} + input FilterTypeInput @doc(description: "FilterTypeInput specifies which action will be performed in a query ") { eq: String @doc(description: "Equals") finset: [String] @doc(description: "Find in set. The value can contain a set of comma-separated values") @@ -30,4 +34,4 @@ type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navig enum SortEnum @doc(description: "This enumeration indicates whether to return results in ascending or descending order") { ASC DESC -} \ No newline at end of file +} diff --git a/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json b/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json deleted file mode 100644 index 37d7a9ffe9f52..0000000000000 --- a/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-grouped-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/GroupedTest.php b/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/GroupedTest.php index 011324fb1cc9f..17e8e4c519ccb 100644 --- a/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/GroupedTest.php +++ b/app/code/Magento/GroupedImportExport/Test/Unit/Model/Import/Product/Type/GroupedTest.php @@ -198,7 +198,7 @@ public function saveDataProvider() ], 'bunch' => [ 'associated_skus' => 'sku_assoc1=1, sku_assoc2=2', - 'sku' => 'productSku', + 'sku' => 'productsku', 'product_type' => 'grouped' ] ], @@ -211,7 +211,7 @@ public function saveDataProvider() ], 'bunch' => [ 'associated_skus' => '', - 'sku' => 'productSku', + 'sku' => 'productsku', 'product_type' => 'grouped' ] ], @@ -219,7 +219,7 @@ public function saveDataProvider() 'skus' => ['newSku' => [],'oldSku' => []], 'bunch' => [ 'associated_skus' => 'sku_assoc1=1, sku_assoc2=2', - 'sku' => 'productSku', + 'sku' => 'productsku', 'product_type' => 'grouped' ] ], @@ -227,13 +227,13 @@ public function saveDataProvider() 'skus' => [ 'newSku' => [ 'sku_assoc1' => ['entity_id' => 1], - 'productSku' => ['entity_id' => 3, 'attr_set_code' => 'Default', 'type_id' => 'grouped'] + 'productsku' => ['entity_id' => 3, 'attr_set_code' => 'Default', 'type_id' => 'grouped'] ], 'oldSku' => [] ], 'bunch' => [ 'associated_skus' => 'sku_assoc1=1', - 'sku' => 'productSku', + 'sku' => 'productsku', 'product_type' => 'simple' ] ] @@ -257,7 +257,7 @@ public function testSaveDataScopeStore() $bunch = [[ 'associated_skus' => 'sku_assoc1=1, sku_assoc2=2', - 'sku' => 'productSku', + 'sku' => 'productsku', 'product_type' => 'grouped' ]]; $this->entityModel->expects($this->at(2))->method('getNextBunch')->will($this->returnValue($bunch)); diff --git a/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php b/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php index a97b1c246741e..e85d4eeca730a 100644 --- a/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php +++ b/app/code/Magento/GroupedProduct/Block/Cart/Item/Renderer/Grouped.php @@ -19,6 +19,8 @@ class Grouped extends Renderer implements IdentityInterface { /** * Path in config to the setting which defines if parent or child product should be used to generate a thumbnail. + * @deprecated moved to model because of class refactoring + * @see \Magento\GroupedProduct\Model\Product\Configuration\Item\ItemProductResolver::CONFIG_THUMBNAIL_SOURCE */ const CONFIG_THUMBNAIL_SOURCE = 'checkout/cart/grouped_product_image'; @@ -36,28 +38,6 @@ public function getGroupedProduct() return $this->getProduct(); } - /** - * {@inheritdoc} - */ - public function getProductForThumbnail() - { - /** - * Show grouped product thumbnail if it must be always shown according to the related setting in system config - * or if child product thumbnail is not available - */ - if ($this->_scopeConfig->getValue( - self::CONFIG_THUMBNAIL_SOURCE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) == ThumbnailSource::OPTION_USE_PARENT_IMAGE || - !($this->getProduct()->getThumbnail() && $this->getProduct()->getThumbnail() != 'no_selection') - ) { - $product = $this->getGroupedProduct(); - } else { - $product = $this->getProduct(); - } - return $product; - } - /** * Return identifiers for produced content * diff --git a/app/code/Magento/GroupedProduct/CustomerData/GroupedItem.php b/app/code/Magento/GroupedProduct/CustomerData/GroupedItem.php deleted file mode 100644 index a38ce3ac65296..0000000000000 --- a/app/code/Magento/GroupedProduct/CustomerData/GroupedItem.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\GroupedProduct\CustomerData; - -use Magento\Catalog\Model\Config\Source\Product\Thumbnail as ThumbnailSource; -use Magento\Checkout\CustomerData\DefaultItem; - -class GroupedItem extends DefaultItem -{ - /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface - */ - protected $_scopeConfig; - - /** - * @param \Magento\Catalog\Helper\Image $imageHelper - * @param \Magento\Msrp\Helper\Data $msrpHelper - * @param \Magento\Framework\UrlInterface $urlBuilder - * @param \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool - * @param \Magento\Checkout\Helper\Data $checkoutHelper - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Framework\Escaper|null $escaper - */ - public function __construct( - \Magento\Catalog\Helper\Image $imageHelper, - \Magento\Msrp\Helper\Data $msrpHelper, - \Magento\Framework\UrlInterface $urlBuilder, - \Magento\Catalog\Helper\Product\ConfigurationPool $configurationPool, - \Magento\Checkout\Helper\Data $checkoutHelper, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Framework\Escaper $escaper = null - ) { - parent::__construct( - $imageHelper, - $msrpHelper, - $urlBuilder, - $configurationPool, - $checkoutHelper, - $escaper - ); - $this->_scopeConfig = $scopeConfig; - } - - /** - * {@inheritdoc} - */ - protected function getProductForThumbnail() - { - /** - * Show grouped product thumbnail if it must be always shown according to the related setting in system config - * or if child product thumbnail is not available - */ - $config = $this->_scopeConfig->getValue( - \Magento\GroupedProduct\Block\Cart\Item\Renderer\Grouped::CONFIG_THUMBNAIL_SOURCE, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - $product = $config == ThumbnailSource::OPTION_USE_PARENT_IMAGE || - (!$this->getProduct()->getThumbnail() || $this->getProduct()->getThumbnail() == 'no_selection') - ? $this->getGroupedProduct() - : $this->getProduct(); - return $product; - } - - /** - * Get item grouped product - * - * @return \Magento\Catalog\Model\Product - */ - protected function getGroupedProduct() - { - $option = $this->item->getOptionByCode('product_type'); - if ($option) { - return $option->getProduct(); - } - return $this->getProduct(); - } -} diff --git a/app/code/Magento/GroupedProduct/Model/Product/Configuration/Item/ItemProductResolver.php b/app/code/Magento/GroupedProduct/Model/Product/Configuration/Item/ItemProductResolver.php new file mode 100644 index 0000000000000..207d9853f8c6b --- /dev/null +++ b/app/code/Magento/GroupedProduct/Model/Product/Configuration/Item/ItemProductResolver.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GroupedProduct\Model\Product\Configuration\Item; + +use Magento\Catalog\Model\Config\Source\Product\Thumbnail; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; + +/** + * {@inheritdoc} + */ +class ItemProductResolver implements ItemResolverInterface +{ + /** + * Path in config to the setting which defines if parent or child product should be used to generate a thumbnail. + */ + const CONFIG_THUMBNAIL_SOURCE = 'checkout/cart/grouped_product_image'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * {@inheritdoc} + */ + public function getFinalProduct(ItemInterface $item) : ProductInterface + { + /** + * Show grouped product thumbnail if it must be always shown according to the related setting in system config + * or if child product thumbnail is not available. + */ + + $childProduct = $item->getProduct(); + $finalProduct = $childProduct; + $parentProduct = $this->getParentProduct($item); + if ($childProduct !== $parentProduct) { + $configValue = $this->scopeConfig->getValue( + self::CONFIG_THUMBNAIL_SOURCE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $childThumb = $childProduct->getData('thumbnail'); + + $finalProduct = + ($configValue == Thumbnail::OPTION_USE_PARENT_IMAGE) || (!$childThumb || $childThumb == 'no_selection') + ? $parentProduct + : $childProduct; + } + return $finalProduct; + } + + /** + * Get grouped product. + * + * @param ItemInterface $item + * @return Product + */ + private function getParentProduct(ItemInterface $item) : Product + { + $option = $item->getOptionByCode('product_type'); + $product = $item->getProduct(); + if ($option) { + $product = $option->getProduct(); + } + return $product; + } +} diff --git a/app/code/Magento/GroupedProduct/Model/Product/Initialization/Helper/ProductLinks/Plugin/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Initialization/Helper/ProductLinks/Plugin/Grouped.php index 6cdcfd248e798..7b9523015c882 100644 --- a/app/code/Magento/GroupedProduct/Model/Product/Initialization/Helper/ProductLinks/Plugin/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Product/Initialization/Helper/ProductLinks/Plugin/Grouped.php @@ -8,6 +8,7 @@ use Magento\Catalog\Api\Data\ProductLinkExtensionFactory; use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\GroupedProduct\Model\Product\Type\Grouped as TypeGrouped; /** @@ -60,6 +61,9 @@ public function __construct( * @param array $links * * @return \Magento\Catalog\Model\Product + * + * @throws NoSuchEntityException + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -70,7 +74,7 @@ public function beforeInitializeLinks( array $links ) { if ($product->getTypeId() === TypeGrouped::TYPE_CODE && !$product->getGroupedReadonly()) { - $links = (isset($links[self::TYPE_NAME])) ? $links[self::TYPE_NAME] : $product->getGroupedLinkData(); + $links = $links[self::TYPE_NAME] ?? $product->getGroupedLinkData(); if (!is_array($links)) { $links = []; } diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php index cbbb58d3c24c8..e1599dc772c2c 100644 --- a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Indexer/Price/Grouped.php @@ -7,140 +7,211 @@ */ namespace Magento\GroupedProduct\Model\ResourceModel\Product\Indexer\Price; -use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructureFactory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\IndexTableStructure; +use Magento\GroupedProduct\Model\ResourceModel\Product\Link; +use Magento\GroupedProduct\Model\Product\Type\Grouped as GroupedType; -class Grouped extends DefaultPrice implements GroupedInterface +/** + * Calculate minimal and maximal prices for Grouped products + * Use calculated price for relation products + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Grouped implements DimensionalIndexerInterface { /** - * Prefix for temporary table support. + * @var IndexTableStructureFactory */ - const TRANSIT_PREFIX = 'transit_'; + private $indexTableStructureFactory; /** - * @inheritdoc + * @var TableMaintainer */ - protected function reindex($entityIds = null) - { - $this->_prepareGroupedProductPriceData($entityIds); + private $tableMaintainer; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var string + */ + private $connectionName; + + /** + * @var AdapterInterface + */ + private $connection; + + /** + * @var bool + */ + private $fullReindexAction; + + /** + * @param IndexTableStructureFactory $indexTableStructureFactory + * @param TableMaintainer $tableMaintainer + * @param MetadataPool $metadataPool + * @param ResourceConnection $resource + * @param string $connectionName + * @param bool $fullReindexAction + */ + public function __construct( + IndexTableStructureFactory $indexTableStructureFactory, + TableMaintainer $tableMaintainer, + MetadataPool $metadataPool, + ResourceConnection $resource, + $connectionName = 'indexer', + $fullReindexAction = false + ) { + $this->indexTableStructureFactory = $indexTableStructureFactory; + $this->tableMaintainer = $tableMaintainer; + $this->connectionName = $connectionName; + $this->metadataPool = $metadataPool; + $this->resource = $resource; + $this->fullReindexAction = $fullReindexAction; } /** - * Calculate minimal and maximal prices for Grouped products - * Use calculated price for relation products - * - * @param int|array $entityIds the parent entity ids limitation - * @return $this + * {@inheritdoc} + * @param array $dimensions + * @param \Traversable $entityIds + * @throws \Exception */ - protected function _prepareGroupedProductPriceData($entityIds = null) + public function executeByDimensions(array $dimensions, \Traversable $entityIds) { - if (!$this->hasEntity() && empty($entityIds)) { - return $this; - } - - $connection = $this->getConnection(); - $table = $this->getIdxTable(); - - if (!$this->tableStrategy->getUseIdxTable()) { - $additionalIdxTable = $connection->getTableName(self::TRANSIT_PREFIX . $this->getIdxTable()); - $connection->createTemporaryTableLike($additionalIdxTable, $table); - $query = $connection->insertFromSelect( - $this->_prepareGroupedProductPriceDataSelect($entityIds), - $additionalIdxTable, - [] - ); - $connection->query($query); - - $select = $connection->select()->from($additionalIdxTable); - $query = $connection->insertFromSelect( - $select, - $table, - [], - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE - ); - $connection->query($query); - $connection->dropTemporaryTable($additionalIdxTable); - } else { - $query = $this->_prepareGroupedProductPriceDataSelect($entityIds)->insertFromSelect($table); - $connection->query($query); - } - return $this; + /** @var IndexTableStructure $temporaryPriceTable */ + $temporaryPriceTable = $this->indexTableStructureFactory->create([ + 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions), + 'entityField' => 'entity_id', + 'customerGroupField' => 'customer_group_id', + 'websiteField' => 'website_id', + 'taxClassField' => 'tax_class_id', + 'originalPriceField' => 'price', + 'finalPriceField' => 'final_price', + 'minPriceField' => 'min_price', + 'maxPriceField' => 'max_price', + 'tierPriceField' => 'tier_price', + ]); + $query = $this->prepareGroupedProductPriceDataSelect($dimensions, iterator_to_array($entityIds)) + ->insertFromSelect($temporaryPriceTable->getTableName()); + $this->getConnection()->query($query); } /** * Prepare data index select for Grouped products prices * - * @param int|array $entityIds the parent entity ids limitation + * @param array $dimensions + * @param array $entityIds the parent entity ids limitation * @return \Magento\Framework\DB\Select + * @throws \Exception */ - protected function _prepareGroupedProductPriceDataSelect($entityIds = null) + private function prepareGroupedProductPriceDataSelect(array $dimensions, array $entityIds) { - $connection = $this->getConnection(); - $table = $this->getIdxTable(); - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); - $select = $connection->select()->from( + $select = $this->getConnection()->select(); + + $select->from( ['e' => $this->getTable('catalog_product_entity')], 'entity_id' - )->joinLeft( + ); + + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $select->joinLeft( ['l' => $this->getTable('catalog_product_link')], - 'e.' . $linkField . ' = l.product_id AND l.link_type_id=' . - \Magento\GroupedProduct\Model\ResourceModel\Product\Link::LINK_TYPE_GROUPED, + 'e.' . $linkField . ' = l.product_id AND l.link_type_id=' . Link::LINK_TYPE_GROUPED, [] - )->join( - ['cg' => $this->getTable('customer_group')], - '', - ['customer_group_id'] ); - $this->_addWebsiteJoinToSelect($select, true); - $this->_addProductWebsiteJoinToSelect($select, 'cw.website_id', 'e.entity_id'); - $minCheckSql = $connection->getCheckSql('le.required_options = 0', 'i.min_price', 0); - $maxCheckSql = $connection->getCheckSql('le.required_options = 0', 'i.max_price', 0); - $select->columns( - 'website_id', - 'cw' - )->joinLeft( + //additional information about inner products + $select->joinLeft( ['le' => $this->getTable('catalog_product_entity')], 'le.entity_id = l.linked_product_id', [] - )->joinLeft( - ['i' => $table], - 'i.entity_id = l.linked_product_id AND i.website_id = cw.website_id' . - ' AND i.customer_group_id = cg.customer_group_id', + ); + $select->columns( + [ + 'i.customer_group_id', + 'i.website_id', + ] + ); + $taxClassId = $this->getConnection()->getCheckSql('MIN(i.tax_class_id) IS NULL', '0', 'MIN(i.tax_class_id)'); + $minCheckSql = $this->getConnection()->getCheckSql('le.required_options = 0', 'i.min_price', 0); + $maxCheckSql = $this->getConnection()->getCheckSql('le.required_options = 0', 'i.max_price', 0); + $select->join( + ['i' => $this->getMainTable($dimensions)], + 'i.entity_id = l.linked_product_id', [ - 'tax_class_id' => $this->getConnection()->getCheckSql( - 'MIN(i.tax_class_id) IS NULL', - '0', - 'MIN(i.tax_class_id)' - ), + 'tax_class_id' => $taxClassId, 'price' => new \Zend_Db_Expr('NULL'), 'final_price' => new \Zend_Db_Expr('NULL'), 'min_price' => new \Zend_Db_Expr('MIN(' . $minCheckSql . ')'), 'max_price' => new \Zend_Db_Expr('MAX(' . $maxCheckSql . ')'), 'tier_price' => new \Zend_Db_Expr('NULL'), ] - )->group( - ['e.entity_id', 'cg.customer_group_id', 'cw.website_id'] - )->where( + ); + $select->group( + ['e.entity_id', 'i.customer_group_id', 'i.website_id'] + ); + $select->where( 'e.type_id=?', - $this->getTypeId() + GroupedType::TYPE_CODE ); if ($entityIds !== null) { $select->where('e.entity_id IN(?)', $entityIds); } - /** - * Add additional external limitation - */ - $this->_eventManager->dispatch( - 'catalog_product_prepare_index_select', - [ - 'select' => $select, - 'entity_field' => new \Zend_Db_Expr('e.entity_id'), - 'website_field' => new \Zend_Db_Expr('cw.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') - ] - ); return $select; } + + /** + * Get main table + * + * @param array $dimensions + * @return string + */ + private function getMainTable($dimensions) + { + if ($this->fullReindexAction) { + return $this->tableMaintainer->getMainReplicaTable($dimensions); + } + return $this->tableMaintainer->getMainTable($dimensions); + } + + /** + * Get connection + * + * return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \DomainException + */ + private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface + { + if ($this->connection === null) { + $this->connection = $this->resource->getConnection($this->connectionName); + } + + return $this->connection; + } + + /** + * Get table + * + * @param string $tableName + * @return string + */ + private function getTable($tableName) + { + return $this->resource->getTableName($tableName, $this->connectionName); + } } diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/AdminGroupedProductActionGroup.xml b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/AdminGroupedProductActionGroup.xml index bf870d3d9e2dd..9360ed6e42792 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/AdminGroupedProductActionGroup.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/AdminGroupedProductActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Check that required fields are actually required--> <actionGroup name="checkRequiredFieldsInGroupedProductForm"> <clearField selector="{{AdminProductFormSection.productName}}" stepKey="clearProductSku"/> @@ -43,4 +43,13 @@ <see selector="{{AdminProductGridSection.firstProductRow}}" userInput="{{product.name}}" stepKey="seeProductNameInGrid"/> <click selector="{{AdminProductGridFilterSection.clearFilters}}" stepKey="clickClearFiltersAfter"/> </actionGroup> + + <!--Fill product min quantity in group products grid--> + <actionGroup name="fillDefaultQuantityForLinkedToGroupProductInGrid"> + <arguments> + <argument name="productName" type="string"/> + <argument name="qty" type="string"/> + </arguments> + <fillField selector="{{AdminAddedProductsToGroupGrid.inputByProductName(productName)}}" userInput="{{qty}}" stepKey="fillDefaultQtyForLinkedProduct"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml index 1f9f1594f8fcb..4d979953a934e 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="GroupedProduct" type="product"> <data key="sku" unique="suffix">groupedproduct</data> <data key="type_id">grouped</data> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml index 2c7cc254c855d..882275d0060b4 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductLinkSimple1" type="product_link"> <var key="sku" entityKey="sku" entityType="product3"/> <var key="linked_product_sku" entityKey="sku" entityType="product"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml index 433dc920502d4..b580c876a6f30 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Qty1000" type="product_link_extension_attribute"> <data key="qty">1000</data> </entity> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml index b712e3f40afd1..68c95e856e2f8 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="OneSimpleProductLink" type="product_links"> <requiredEntity type="product_link">ProductLinkSimple1</requiredEntity> </entity> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Page/AdminProductCreatePage.xml index d8edc37160bab..7cf998cff3eea 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Page/AdminProductCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> <section name="AdminProductFormGroupedProductsSection"/> <section name="AdminAddProductsToGroupPanel"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminAddProductsToGroupPanelSection.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminAddProductsToGroupPanelSection.xml index a03b1b34c1a5b..e2c4286135d2e 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminAddProductsToGroupPanelSection.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminAddProductsToGroupPanelSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminAddProductsToGroupPanel"> <element name="addSelectedProducts" type="button" selector=".product_form_product_form_grouped_grouped_products_modal button.action-primary" timeout="30"/> <element name="filters" type="button" selector=".product_form_product_form_grouped_grouped_products_modal [data-action='grid-filter-expand']" timeout="30"/> @@ -16,4 +16,8 @@ <element name="firstCheckbox" type="input" selector="tr[data-repeat-index='0'] .admin__control-checkbox"/> <element name="nThCheckbox" type="input" selector="tr[data-repeat-index='{{n}}'] .admin__control-checkbox" parameterized="true"/> </section> + + <section name="AdminAddedProductsToGroupGrid"> + <element name="inputByProductName" type="input" selector="//div[@data-index='grouped']//table//tr[td[@data-index='name']//span[text()='{{productName}}']]//td[@data-index='qty']//input" parameterized="true"/> + </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml index adb0ac5a984a1..64dcd9566d890 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductFormGroupedProductsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormGroupedProductsSection"> <element name="toggleGroupedProduct" type="button" selector="div[data-index=grouped] .admin__collapsible-title"/> <element name="addProductsToGroup" type="button" selector="button[data-index='grouped_products_button']" timeout="30"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml index 1bee1846ac0f5..5d65f82690235 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultImageGroupedProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultImageGroupedProductTest"> <annotations> <features value="GroupedProduct"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml new file mode 100644 index 0000000000000..8634fc3f6f9dc --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoGroupedProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to add default video for a Grouped Product"/> + <description value="Admin should be able to add default video for a Grouped Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-108"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Create a grouped product --> + <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <actionGroup ref="fillGroupedProductForm" stepKey="fillMainProductForm"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + + <!-- Add two simple products to grouped product --> + <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductSection" after="scrollToSection"/> + <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup" after="openGroupedProductSection"/> + <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForFilter" after="clickAddProductsToGroup"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku1" after="waitForFilter"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku2" after="checkOption1"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> + <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="addSelectedProducts" before="saveProductForm"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductSetEditContentTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductSetEditContentTest.xml new file mode 100644 index 0000000000000..3938d91c515f1 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductSetEditContentTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminGroupedProductSetEditContentTest" extends="AdminSimpleProductSetEditContentTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Create/edit grouped product"/> + <title value="Admin should be able to set/edit product Content when editing a grouped product"/> + <description value="Admin should be able to set/edit product Content when editing a grouped product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3423"/> + <group value="GroupedProduct"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <after> + <!-- Delete grouped product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillProductForm"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + + <!--Checking content storefront--> + <amOnPage url="{{GroupedProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedSetEditRelatedProductsTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedSetEditRelatedProductsTest.xml new file mode 100644 index 0000000000000..8117d627a370c --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedSetEditRelatedProductsTest.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminGroupedSetEditRelatedProductsTest" extends="AdminSimpleSetEditRelatedProductsTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Create/edit grouped product"/> + <title value="Admin should be able to set/edit Related Products information when editing a grouped product"/> + <description value="Admin should be able to set/edit Related Products information when editing a grouped product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3755"/> + <group value="GroupedProduct"/> + </annotations> + <before></before> + <after> + <!-- Delete grouped product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + </after> + + <!-- Create product --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <actionGroup ref="fillGroupedProductForm" stepKey="fillProductForm"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + + <!--See related product in storefront--> + <amOnPage url="{{GroupedProduct.sku}}.html" stepKey="goToStorefront"/> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml index a0408a744cc7e..da7cfaeb71566 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultImageGroupedProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultImageGroupedProductTest"> <annotations> <features value="GroupedProduct"/> @@ -69,9 +69,7 @@ <!-- Remove image from product --> <actionGroup ref="removeProductImage" stepKey="removeProductImage"/> - <!-- Skip success message check when saving product because of bug MAGETWO-91177 --> - <!-- actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/--> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductFormAfterRemove"/> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> <!-- Assert product image not in admin product form --> <actionGroup ref="assertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml new file mode 100644 index 0000000000000..25c45abdfe047 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoGroupedProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Add/remove images and videos for all product types and category"/> + <title value="Admin should be able to remove default video from a Grouped Product"/> + <description value="Admin should be able to remove default video from a Grouped Product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-203"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Create a grouped product --> + <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + <actionGroup ref="fillGroupedProductForm" stepKey="fillMainProductForm"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + + <!-- Add two simple products to grouped product --> + <scrollTo selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" x="0" y="-100" stepKey="scrollToSection" after="addProductVideo"/> + <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductSection" after="scrollToSection"/> + <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup" after="openGroupedProductSection"/> + <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForFilter" after="clickAddProductsToGroup"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku1" after="waitForFilter"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption1" after="filterProductGridBySku1"/> + <actionGroup ref="filterProductGridBySku" stepKey="filterProductGridBySku2" after="checkOption1"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="checkOption2" after="filterProductGridBySku2"/> + <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="addSelectedProducts" before="saveProductForm"/> + + <!-- Assert product in storefront product page --> + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPage" stepKey="AssertProductInStorefrontProductPage"> + <argument name="product" value="GroupedProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml new file mode 100644 index 0000000000000..0fd52ac4a65a4 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchGroupedProductByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Grouped product with product name"/> + <description value="Guest customer should be able to advance search Grouped product with product name"/> + <severity value="MAJOR"/> + <testCaseId value="MC-141"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchGroupedProductBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Grouped product with product sku"/> + <description value="Guest customer should be able to advance search Grouped product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-146"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchGroupedProductByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Grouped product with product description"/> + <description value="Guest customer should be able to advance search Grouped product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-282"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchGroupedProductByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Grouped product with product short description"/> + <description value="Guest customer should be able to advance search Grouped product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-283"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> + <test name="AdvanceCatalogSearchGroupedProductByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Grouped product with product price"/> + <description value="Guest customer should be able to advance search Grouped product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-284"/> + <group value="GroupedProduct"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiGroupedProduct" stepKey="product"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <updateData entity="OneMoreSimpleProductLink" createDataKey="addProductOne" stepKey="addProductTwo"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="simple2"/> + </updateData> + <getData entity="GetProduct3" stepKey="arg1"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg2"> + <requiredEntity createDataKey="simple1"/> + </getData> + <getData entity="GetProduct" stepKey="arg3"> + <requiredEntity createDataKey="simple2"/> + </getData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml index 60aa666224550..dbe3dddfca81b 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <!--Create Grouped Product--> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPageGrouped" after="seeSimpleProductInGrid"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/NewProductsListWidgetGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/NewProductsListWidgetGroupedProductTest.xml new file mode 100644 index 0000000000000..a56512f84a9b8 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/NewProductsListWidgetGroupedProductTest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetGroupedProductTest" extends="NewProductsListWidgetTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set Grouped Product as new so that it shows up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set Grouped Product as new so that it shows up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <testCaseId value="MC-121"/> + <group value="GroupedProduct"/> + <group value="WYSIWYGDisabled"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProductOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiSimpleProduct" stepKey="createProductTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <after> + <deleteData createDataKey="createProductOne" stepKey="deleteProductOne"/> + <deleteData createDataKey="createProductTwo" stepKey="deleteProductTwo"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + </after> + + <!-- A Cms page containing the New Products Widget gets created here via extends --> + + <!-- Create a grouped product to appear in the widget --> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="toggleAddProductButton"/> + <click selector="{{AdminProductGridActionSection.addGroupedProduct}}" stepKey="clickAddGroupedProduct"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{_defaultProduct.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{_defaultProduct.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewFrom}}" userInput="01/1/2000" stepKey="fillProductNewFrom"/> + <fillField selector="{{AdminProductFormSection.setProductAsNewTo}}" userInput="01/1/2099" stepKey="fillProductNewTo"/> + <conditionalClick selector="{{AdminProductFormGroupedProductsSection.toggleGroupedProduct}}" dependentSelector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" visible="false" stepKey="openGroupedProductsSection"/> + <scrollTo selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="scrollToAddProductsToGroup"/> + <click selector="{{AdminProductFormGroupedProductsSection.addProductsToGroup}}" stepKey="clickAddProductsToGroup"/> + <waitForElementVisible selector="{{AdminAddProductsToGroupPanel.filters}}" stepKey="waitForGroupedProductModal"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterGroupedProducts"> + <argument name="sku" value="api-simple-product"/> + </actionGroup> + <checkOption selector="{{AdminAddProductsToGroupPanel.nThCheckbox('0')}}" stepKey="checkFilterResult1"/> + <checkOption selector="{{AdminAddProductsToGroupPanel.nThCheckbox('1')}}" stepKey="checkFilterResult2"/> + <click selector="{{AdminAddProductsToGroupPanel.addSelectedProducts}}" stepKey="clickAddSelectedGroupProducts"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + + <!-- If PageCache is enabled, Cache clearing happens here, via merge --> + + <!-- Check for product on the CMS page with the New Products widget --> + + <amOnPage url="{{_newDefaultCmsPage.identifier}}" stepKey="amOnCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPage"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{_defaultProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/composer.json b/app/code/Magento/GroupedProduct/Test/Mftf/composer.json deleted file mode 100644 index ebd0d3656952f..0000000000000 --- a/app/code/Magento/GroupedProduct/Test/Mftf/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "magento/functional-test-module-grouped-product", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-grouped-product-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Block/Cart/Item/Renderer/GroupedTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Block/Cart/Item/Renderer/GroupedTest.php index 5dc12fe39c46a..9302706342f13 100644 --- a/app/code/Magento/GroupedProduct/Test/Unit/Block/Cart/Item/Renderer/GroupedTest.php +++ b/app/code/Magento/GroupedProduct/Test/Unit/Block/Cart/Item/Renderer/GroupedTest.php @@ -11,128 +11,22 @@ class GroupedTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_scopeConfig; + private $scopeConfig; /** @var Renderer */ - protected $_renderer; + private $renderer; protected function setUp() { parent::setUp(); $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->_scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); - $this->_renderer = $objectManagerHelper->getObject( + $this->scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->renderer = $objectManagerHelper->getObject( \Magento\GroupedProduct\Block\Cart\Item\Renderer\Grouped::class, - ['scopeConfig' => $this->_scopeConfig] + ['scopeConfig' => $this->scopeConfig] ); } - /** - * Child thumbnail is available and config option is not set to use parent thumbnail. - */ - public function testGetProductForThumbnail() - { - $childHasThumbnail = true; - $useParentThumbnail = false; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['childProduct'], - $productForThumbnail, - 'Child product was expected to be returned.' - ); - } - - /** - * Child thumbnail is not available and config option is not set to use parent thumbnail. - */ - public function testGetProductForThumbnailChildThumbnailNotAvailable() - { - $childHasThumbnail = false; - $useParentThumbnail = false; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['parentProduct'], - $productForThumbnail, - 'Parent product was expected to be returned.' - ); - } - - /** - * Child thumbnail is available and config option is set to use parent thumbnail. - */ - public function testGetProductForThumbnailConfigUseParent() - { - $childHasThumbnail = true; - $useParentThumbnail = true; - $products = $this->_initProducts($childHasThumbnail, $useParentThumbnail); - - $productForThumbnail = $this->_renderer->getProductForThumbnail(); - $this->assertSame( - $products['parentProduct'], - $productForThumbnail, - 'Parent product was expected to be returned ' . - 'if "checkout/cart/grouped_product_image" is set to "parent" in system config.' - ); - } - - /** - * Initialize parent grouped product and child product. - * - * @param bool $childHasThumbnail - * @param bool $useParentThumbnail - * @return \Magento\Catalog\Model\Product[]|\PHPUnit_Framework_MockObject_MockObject[] - */ - protected function _initProducts($childHasThumbnail = true, $useParentThumbnail = false) - { - /** Set option which can force usage of parent product thumbnail when grouped product is displayed */ - $thumbnailToBeUsed = $useParentThumbnail - ? ThumbnailSource::OPTION_USE_PARENT_IMAGE - : ThumbnailSource::OPTION_USE_OWN_IMAGE; - $this->_scopeConfig->expects( - $this->any() - )->method( - 'getValue' - )->with( - Renderer::CONFIG_THUMBNAIL_SOURCE - )->will( - $this->returnValue($thumbnailToBeUsed) - ); - - /** Initialized parent product */ - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $parentProduct */ - $parentProduct = $this->createMock(\Magento\Catalog\Model\Product::class); - - /** Initialize child product */ - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $childProduct */ - $childProduct = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['getThumbnail', '__wakeup']); - $childThumbnail = $childHasThumbnail ? 'thumbnail.jpg' : 'no_selection'; - $childProduct->expects($this->any())->method('getThumbnail')->will($this->returnValue($childThumbnail)); - - /** Mock methods which return parent and child products */ - /** @var \Magento\Quote\Model\Quote\Item\Option|\PHPUnit_Framework_MockObject_MockObject $itemOption */ - $itemOption = $this->createMock(\Magento\Quote\Model\Quote\Item\Option::class); - $itemOption->expects($this->any())->method('getProduct')->will($this->returnValue($parentProduct)); - /** @var \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject $item */ - $item = $this->createMock(\Magento\Quote\Model\Quote\Item::class); - $item->expects($this->any())->method('getProduct')->will($this->returnValue($childProduct)); - $item->expects( - $this->any() - )->method( - 'getOptionByCode' - )->with( - 'product_type' - )->will( - $this->returnValue($itemOption) - ); - $this->_renderer->setItem($item); - - return ['parentProduct' => $parentProduct, 'childProduct' => $childProduct]; - } - public function testGetIdentities() { $productTags = ['catalog_product_1']; @@ -140,7 +34,7 @@ public function testGetIdentities() $product->expects($this->exactly(2))->method('getIdentities')->will($this->returnValue($productTags)); $item = $this->createMock(\Magento\Quote\Model\Quote\Item::class); $item->expects($this->exactly(2))->method('getProduct')->will($this->returnValue($product)); - $this->_renderer->setItem($item); - $this->assertEquals(array_merge($productTags, $productTags), $this->_renderer->getIdentities()); + $this->renderer->setItem($item); + $this->assertEquals(array_merge($productTags, $productTags), $this->renderer->getIdentities()); } } diff --git a/app/code/Magento/GroupedProduct/etc/adminhtml/system.xml b/app/code/Magento/GroupedProduct/etc/adminhtml/system.xml index bbb2054abb14f..5854928afc2fd 100644 --- a/app/code/Magento/GroupedProduct/etc/adminhtml/system.xml +++ b/app/code/Magento/GroupedProduct/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="checkout" translate="label" type="text" sortOrder="305" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="cart" translate="label" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> - <field id="grouped_product_image" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="grouped_product_image" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Grouped Product Image</label> <source_model>Magento\Catalog\Model\Config\Source\Product\Thumbnail</source_model> </field> diff --git a/app/code/Magento/GroupedProduct/etc/di.xml b/app/code/Magento/GroupedProduct/etc/di.xml index 8f688e3d06b00..43678d0ad7a82 100644 --- a/app/code/Magento/GroupedProduct/etc/di.xml +++ b/app/code/Magento/GroupedProduct/etc/di.xml @@ -88,8 +88,6 @@ </argument> </arguments> </type> - <preference for="Magento\GroupedProduct\Model\ResourceModel\Product\Indexer\Price\GroupedInterface" - type="Magento\GroupedProduct\Model\ResourceModel\Product\Indexer\Price\Grouped"/> <type name="Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator"> <arguments> <argument name="estimators" xsi:type="array"> @@ -100,4 +98,11 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product\Configuration\Item\ItemResolverComposite"> + <arguments> + <argument name="itemResolvers" xsi:type="array"> + <item name="grouped" xsi:type="string">Magento\GroupedProduct\Model\Product\Configuration\Item\ItemProductResolver</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/GroupedProduct/etc/frontend/di.xml b/app/code/Magento/GroupedProduct/etc/frontend/di.xml deleted file mode 100644 index 755d53f46bfdd..0000000000000 --- a/app/code/Magento/GroupedProduct/etc/frontend/di.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\Checkout\CustomerData\ItemPoolInterface"> - <arguments> - <argument name="itemMap" xsi:type="array"> - <item name="grouped" xsi:type="string">Magento\GroupedProduct\CustomerData\GroupedItem</item> - </argument> - </arguments> - </type> -</config> diff --git a/app/code/Magento/GroupedProduct/etc/product_types.xml b/app/code/Magento/GroupedProduct/etc/product_types.xml index 8f41611353a32..9f8f0534ff34f 100644 --- a/app/code/Magento/GroupedProduct/etc/product_types.xml +++ b/app/code/Magento/GroupedProduct/etc/product_types.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd"> <type name="grouped" label="Grouped Product" modelInstance="Magento\GroupedProduct\Model\Product\Type\Grouped" composite='true' indexPriority="50" sortOrder="30"> <priceModel instance="Magento\GroupedProduct\Model\Product\Type\Grouped\Price" /> - <indexerModel instance="Magento\GroupedProduct\Model\ResourceModel\Product\Indexer\Price\GroupedInterface" /> + <indexerModel instance="Magento\GroupedProduct\Model\ResourceModel\Product\Indexer\Price\Grouped" /> <stockIndexerModel instance="Magento\GroupedProduct\Model\ResourceModel\Indexer\Stock\Grouped" /> <customAttributes> <attribute name="is_real_product" value="false"/> diff --git a/app/code/Magento/GroupedProductGraphQl/Test/Mftf/composer.json b/app/code/Magento/GroupedProductGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 6335b45595db7..0000000000000 --- a/app/code/Magento/GroupedProductGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-grouped-product-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ImportExport/Files/Sample/customer.csv b/app/code/Magento/ImportExport/Files/Sample/customer.csv index 64c09574aea73..522e59f566739 100644 --- a/app/code/Magento/ImportExport/Files/Sample/customer.csv +++ b/app/code/Magento/ImportExport/Files/Sample/customer.csv @@ -1,2 +1,2 @@ -email,_website,_store,confirmation,created_at,created_in,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,reward_update_notification,reward_warning_notification,rp_token,rp_token_created_at,store_id,suffix,taxvat,updated_at,website_id,password -jondoe@example.com,base,default,,"2015-10-30 12:49:47","Default Store View",0,,Jon,,1,Doe,,d708be3fe0fe0120840e8b13c8faae97424252c6374227ff59c05814f1aecd79:mgLqkqgTwLPLlCljzvF8hp67fNOOvOZb:1,,,,07e71459c137f4da15292134ff459cba,"2015-10-30 12:49:48",1,,,"2015-10-30 12:49:48",1, +email,_website,_store,confirmation,created_at,created_in,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,rp_token,rp_token_created_at,store_id,suffix,taxvat,updated_at,website_id,password +jondoe@example.com,base,default,,"2015-10-30 12:49:47","Default Store View",0,,Jon,,1,Doe,,d708be3fe0fe0120840e8b13c8faae97424252c6374227ff59c05814f1aecd79:mgLqkqgTwLPLlCljzvF8hp67fNOOvOZb:1,,07e71459c137f4da15292134ff459cba,"2015-10-30 12:49:48",1,,,"2015-10-30 12:49:48",1, diff --git a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php index e7883693fbe74..e965e8ad207fd 100644 --- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php +++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php @@ -391,6 +391,7 @@ protected function _saveValidatedBunches() $nextRowBackup = []; $maxDataSize = $this->_resourceHelper->getMaxDataSize(); $bunchSize = $this->_importExportData->getBunchSize(); + $skuSet = []; $source->rewind(); $this->_dataSourceModel->cleanBunches(); @@ -407,6 +408,7 @@ protected function _saveValidatedBunches() if ($source->valid()) { try { $rowData = $source->current(); + $skuSet[$rowData['sku']] = true; } catch (\InvalidArgumentException $e) { $this->addRowError($e->getMessage(), $this->_processedRowsCount); $this->_processedRowsCount++; @@ -434,6 +436,8 @@ protected function _saveValidatedBunches() $source->next(); } } + $this->_processedEntitiesCount = count($skuSet); + return $this; } diff --git a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php index 079b2e60c4785..7f49e2022c410 100644 --- a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php +++ b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php @@ -77,7 +77,7 @@ public function addError( $errorMessage = null, $errorDescription = null ) { - if ($this->isErrorAlreadyAdded($rowNumber, $errorCode)) { + if ($this->isErrorAlreadyAdded($rowNumber, $errorCode, $columnName)) { return $this; } $this->processErrorStatistics($errorLevel); @@ -333,13 +333,14 @@ public function clear() /** * @param int $rowNum * @param string $errorCode + * @param string $columnName * @return bool */ - protected function isErrorAlreadyAdded($rowNum, $errorCode) + protected function isErrorAlreadyAdded($rowNum, $errorCode, $columnName = null) { $errors = $this->getErrorsByCode([$errorCode]); foreach ($errors as $error) { - if ($rowNum == $error->getRowNumber()) { + if ($rowNum == $error->getRowNumber() && $columnName == $error->getColumnName()) { return true; } } diff --git a/app/code/Magento/ImportExport/Test/Mftf/composer.json b/app/code/Magento/ImportExport/Test/Mftf/composer.json deleted file mode 100644 index fcce7ec7e84ff..0000000000000 --- a/app/code/Magento/ImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ImportExport/etc/db_schema_whitelist.json b/app/code/Magento/ImportExport/etc/db_schema_whitelist.json index d8f51dd4ef387..e78535d2c7585 100644 --- a/app/code/Magento/ImportExport/etc/db_schema_whitelist.json +++ b/app/code/Magento/ImportExport/etc/db_schema_whitelist.json @@ -1,27 +1,27 @@ { - "importexport_importdata": { - "column": { - "id": true, - "entity": true, - "behavior": true, - "data": true + "importexport_importdata": { + "column": { + "id": true, + "entity": true, + "behavior": true, + "data": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true + "import_history": { + "column": { + "history_id": true, + "started_at": true, + "user_id": true, + "imported_file": true, + "execution_time": true, + "summary": true, + "error_file": true + }, + "constraint": { + "PRIMARY": true + } } - }, - "import_history": { - "column": { - "history_id": true, - "started_at": true, - "user_id": true, - "imported_file": true, - "execution_time": true, - "summary": true, - "error_file": true - }, - "constraint": { - "PRIMARY": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml index 093830d6fdb1c..fbdd394783608 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml @@ -64,6 +64,7 @@ require([ this.modifyFilterGrid(); } } else { + this.previousGridEntity = ''; $('export_filter_container').hide(); } } diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml index 73b013fdb8df4..8a52f4ca88e75 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml @@ -178,9 +178,13 @@ require([ .loader('show'); var form = jQuery('#edit_form') .one('invalid-form.validate', function(e){jQuery('body').loader('hide')}); - newActionUrl = (newActionUrl ? newActionUrl : form.attr('action')) + - (form.attr('action').lastIndexOf('?') != -1 ? '&' : '?')+ - 'form_key=' + encodeURIComponent(form.find('[name="form_key"]').val()); + + newActionUrl = (newActionUrl ? newActionUrl : form.attr('action')); + if (newActionUrl.lastIndexOf('form_key') === -1) { + newActionUrl = newActionUrl + + (newActionUrl.lastIndexOf('?') !== -1 ? '&' : '?') + + 'form_key=' + encodeURIComponent(form.find('[name="form_key"]').val()); + } form.trigger('save', [{ action: newActionUrl, diff --git a/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php new file mode 100644 index 0000000000000..51d67e2116a06 --- /dev/null +++ b/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php @@ -0,0 +1,207 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Console\Cli; +use Magento\Indexer\Model\ModeSwitcherInterface; + +/** + * Command to set indexer dimensions mode + */ +class IndexerSetDimensionsModeCommand extends AbstractIndexerCommand +{ + const INPUT_KEY_MODE = 'mode'; + const INPUT_KEY_INDEXER = 'indexer'; + const DIMENSION_MODE_NONE = 'none'; + const XML_PATH_DIMENSIONS_MODE_MASK = 'indexer/%s/dimensions_mode'; + + /** + * @var string + */ + private $commandName = 'indexer:set-dimensions-mode'; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface + */ + private $configReader; + + /** + * @var ModeSwitcherInterface[] + */ + private $dimensionProviders; + + /** + * @param ObjectManagerFactory $objectManagerFactory + * @param ScopeConfigInterface $configReader + * @param ModeSwitcherInterface[] $dimensionSwitchers + */ + public function __construct( + ObjectManagerFactory $objectManagerFactory, + ScopeConfigInterface $configReader, + array $dimensionSwitchers + ) { + $this->configReader = $configReader; + $this->dimensionProviders = $dimensionSwitchers; + parent::__construct($objectManagerFactory); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName($this->commandName) + ->setDescription('Set Indexer Dimensions Mode') + ->setDefinition($this->getInputList()); + parent::configure(); + } + + /** + * {@inheritdoc} + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $errors = $this->validate($input); + if ($errors) { + throw new \InvalidArgumentException(implode(PHP_EOL, $errors)); + } + $returnValue = Cli::RETURN_SUCCESS; + /** @var \Magento\Indexer\Model\Indexer $indexer */ + $indexer = $this->getObjectManager()->get(\Magento\Indexer\Model\Indexer::class); + try { + $selectedIndexer = (string)$input->getArgument(self::INPUT_KEY_INDEXER); + if (!$selectedIndexer) { + $this->showAvailableModes($output); + } else { + $indexer->load($selectedIndexer); + $currentMode = $input->getArgument(self::INPUT_KEY_MODE); + $configPath = sprintf(self::XML_PATH_DIMENSIONS_MODE_MASK, $selectedIndexer); + $previousMode = $this->configReader->getValue($configPath) ?: self::DIMENSION_MODE_NONE; + if ($previousMode !== $currentMode) { + /** @var ModeSwitcherInterface $modeSwitcher */ + $modeSwitcher = $this->dimensionProviders[$selectedIndexer]; + // Switch dimensions mode + $modeSwitcher->switchMode($currentMode, $previousMode); + $output->writeln( + 'Dimensions mode for indexer "' . $indexer->getTitle() . '" was changed from \'' + . $previousMode . '\' to \'' . $currentMode . '\'' + ); + } else { + $output->writeln('Dimensions mode for indexer "' . $indexer->getTitle() . '" has not been changed'); + } + } + } catch (\Exception $e) { + $output->writeln('"' . $indexer->getTitle() . '" indexer process unknown error:' . PHP_EOL); + $output->writeln($e->getMessage() . PHP_EOL); + // we must have an exit code higher than zero to indicate something was wrong + $returnValue = Cli::RETURN_FAILURE; + } + + return $returnValue; + } + + /** + * Display all available indexers and modes + * + * @param OutputInterface $output + * @return void + */ + private function showAvailableModes(OutputInterface $output) + { + $output->writeln(sprintf('%-50s', 'Indexer') . 'Available modes'); + foreach ($this->dimensionProviders as $indexer => $provider) { + $availableModes = implode(',', array_keys($provider->getDimensionModes()->getDimensions())); + $output->writeln(sprintf('%-50s', $indexer) . $availableModes); + } + } + + /** + * Get list of arguments for the command + * + * @return InputArgument[] + */ + private function getInputList(): array + { + $dimensionProvidersList = array_keys($this->dimensionProviders); + $indexerOptionDescription = 'Indexer name [' . implode('|', $dimensionProvidersList) . ']'; + $arguments[] = new InputArgument( + self::INPUT_KEY_INDEXER, + InputArgument::OPTIONAL, + $indexerOptionDescription + ); + $modeOptionDescription = 'Indexer dimension modes' . PHP_EOL; + foreach ($this->dimensionProviders as $indexer => $provider) { + $availableModes = implode(',', array_keys($provider->getDimensionModes()->getDimensions())); + $modeOptionDescription .= sprintf('%-30s ', $indexer) . $availableModes . PHP_EOL; + } + $arguments[] = new InputArgument( + self::INPUT_KEY_MODE, + InputArgument::OPTIONAL, + $modeOptionDescription + ); + + return $arguments; + } + + /** + * Check if all arguments are provided + * + * @param InputInterface $input + * @return string[] + */ + private function validate(InputInterface $input): array + { + $errors = []; + $inputIndexer = (string)$input->getArgument(self::INPUT_KEY_INDEXER); + if ($inputIndexer) { + $acceptedValues = array_keys($this->dimensionProviders); + $errors = $this->validateArgument(self::INPUT_KEY_INDEXER, $inputIndexer, $acceptedValues); + if (!$errors) { + $inputIndexerDimensionMode = (string)$input->getArgument(self::INPUT_KEY_MODE); + /** @var ModeSwitcherInterface $modeSwitcher */ + $modeSwitcher = $this->dimensionProviders[$inputIndexer]; + $acceptedValues = array_keys($modeSwitcher->getDimensionModes()->getDimensions()); + $errors = $this->validateArgument(self::INPUT_KEY_MODE, $inputIndexerDimensionMode, $acceptedValues); + } + } + + return $errors; + } + + /** + * Validate command argument and return errors in case if argument is invalid + * + * @param string $inputKey + * @param string $inputIndexer + * @param array $acceptedValues + * @return string[] + */ + private function validateArgument(string $inputKey, string $inputIndexer, array $acceptedValues): array + { + $errors = []; + $acceptedIndexerValues = ' Accepted values for "<' . $inputKey . '>" are \'' . + implode(',', $acceptedValues) . '\''; + if (!$inputIndexer) { + $errors[] = 'Missing argument "<' . $inputKey . '>".' . $acceptedIndexerValues; + } elseif (!\in_array($inputIndexer, $acceptedValues)) { + $errors[] = 'Invalid value for "<' . $inputKey . '>" argument.' . $acceptedIndexerValues; + } + + return $errors; + } +} diff --git a/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php new file mode 100644 index 0000000000000..44fff0bca6802 --- /dev/null +++ b/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php @@ -0,0 +1,155 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Console\Cli; +use Symfony\Component\Console\Input\InputArgument; + +/** + * Command to show indexers dimension modes + */ +class IndexerShowDimensionsModeCommand extends AbstractIndexerCommand +{ + const INPUT_KEY_INDEXER = 'indexer'; + const DIMENSION_MODE_NONE = 'none'; + const XML_PATH_DIMENSIONS_MODE_MASK = 'indexer/%s/dimensions_mode'; + /** + * @var string + */ + private $commandName = 'indexer:show-dimensions-mode'; + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface + */ + private $configReader; + /** + * @var string[] + */ + private $indexers; + + /** + * @param ObjectManagerFactory $objectManagerFactory + * @param ScopeConfigInterface $configReader + * @param array $indexers + */ + public function __construct( + ObjectManagerFactory $objectManagerFactory, + ScopeConfigInterface $configReader, + array $indexers + ) { + $this->configReader = $configReader; + $this->indexers = $indexers; + parent::__construct($objectManagerFactory); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName($this->commandName) + ->setDescription('Shows Indexer Dimension Mode') + ->setDefinition($this->getInputList()); + parent::configure(); + } + + /** + * {@inheritdoc} + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $errors = $this->validate($input); + if ($errors) { + throw new \InvalidArgumentException(implode(PHP_EOL, $errors)); + } + $returnValue = Cli::RETURN_SUCCESS; + /** @var \Magento\Indexer\Model\Indexer $indexer */ + $indexer = $this->getObjectManager()->get(\Magento\Indexer\Model\Indexer::class); + try { + $selectedIndexers = $input->getArgument(self::INPUT_KEY_INDEXER); + if ($selectedIndexers) { + $indexersList = (array)$selectedIndexers; + } else { + $indexersList = $this->indexers; + } + foreach ($indexersList as $indexerId) { + $indexer->load($indexerId); + $configPath = sprintf(self::XML_PATH_DIMENSIONS_MODE_MASK, $indexerId); + $mode = $this->configReader->getValue($configPath) ?: self::DIMENSION_MODE_NONE; + $output->writeln(sprintf('%-50s ', $indexer->getTitle() . ':') . $mode); + } + } catch (\Exception $e) { + $output->writeln('"' . $indexer->getTitle() . '" indexer process unknown error:' . PHP_EOL); + $output->writeln($e->getMessage() . PHP_EOL); + // we must have an exit code higher than zero to indicate something was wrong + $returnValue = Cli::RETURN_FAILURE; + } + + return $returnValue; + } + + /** + * Get list of arguments for the command + * + * @return InputArgument[] + */ + private function getInputList(): array + { + $optionDescription = 'Space-separated list of index types or omit to apply to all indexes'; + $arguments[] = new InputArgument( + self::INPUT_KEY_INDEXER, + InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + $optionDescription . ' (' . implode($this->indexers) . ')' + ); + + return $arguments; + } + + /** + * Check if all arguments are provided + * + * @param InputInterface $input + * @return string[] + */ + private function validate(InputInterface $input): array + { + $inputIndexer = (array)$input->getArgument(self::INPUT_KEY_INDEXER); + $acceptedValues = array_keys($this->indexers); + $errors = $this->validateArgument(self::INPUT_KEY_INDEXER, $inputIndexer, $acceptedValues); + + return $errors; + } + + /** + * Validate command argument and return errors in case if argument is invalid + * + * @param string $inputKey + * @param array $inputIndexer + * @param array $acceptedValues + * @return array + */ + private function validateArgument(string $inputKey, array $inputIndexer, array $acceptedValues): array + { + $errors = []; + $acceptedIndexerValues = ' Accepted values for "<' . $inputKey . '>" are \'' . + implode(',', $acceptedValues) . '\''; + if (!empty($inputIndexer) && !\array_intersect($inputIndexer, $acceptedValues)) { + $errors[] = 'Invalid value for "<' . $inputKey . '>" argument.' . $acceptedIndexerValues; + } + + return $errors; + } +} diff --git a/app/code/Magento/Indexer/Model/DimensionMode.php b/app/code/Magento/Indexer/Model/DimensionMode.php new file mode 100644 index 0000000000000..5e67f70663bac --- /dev/null +++ b/app/code/Magento/Indexer/Model/DimensionMode.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * DTO to work with dimension mode + */ +class DimensionMode +{ + /** + * @var array + */ + private $name; + + /** + * @var array + */ + private $dimensions; + + /** + * @param string $name + * @param array $dimensions + */ + public function __construct(string $name, array $dimensions) + { + $this->dimensions = (function (string ...$dimensions) { + return $dimensions; + })(...$dimensions); + $this->name = $name; + } + + /** + * Returns dimension name + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns dimension modes + * + * @return string[] + */ + public function getDimensions(): array + { + return $this->dimensions; + } +} diff --git a/app/code/Magento/Indexer/Model/DimensionModes.php b/app/code/Magento/Indexer/Model/DimensionModes.php new file mode 100644 index 0000000000000..bbdee0ced8662 --- /dev/null +++ b/app/code/Magento/Indexer/Model/DimensionModes.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * DTO to work with dimension modes + */ +class DimensionModes +{ + /** + * @var DimensionMode[] + */ + private $dimensions; + + /** + * @param DimensionMode[] $dimensions + */ + public function __construct(array $dimensions) + { + $this->dimensions = (function (DimensionMode ...$dimensions) { + $result = []; + foreach ($dimensions as $dimension) { + $result[$dimension->getName()] = $dimension; + }; + return $result; + })(...$dimensions); + } + + /** + * Returns dimensions and their modes + * + * @return array + */ + public function getDimensions(): array + { + return $this->dimensions; + } +} diff --git a/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php b/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php index d22795f127138..829df74a1b0ed 100644 --- a/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php +++ b/app/code/Magento/Indexer/Model/Indexer/DependencyDecorator.php @@ -256,7 +256,10 @@ public function reindexRow($id) $this->indexer->reindexRow($id); $dependentIndexerIds = $this->dependencyInfoProvider->getIndexerIdsToRunAfter($this->indexer->getId()); foreach ($dependentIndexerIds as $indexerId) { - $this->indexerRegistry->get($indexerId)->reindexRow($id); + $dependentIndexer = $this->indexerRegistry->get($indexerId); + if (!$dependentIndexer->isScheduled()) { + $dependentIndexer->reindexRow($id); + } } } @@ -268,7 +271,10 @@ public function reindexList($ids) $this->indexer->reindexList($ids); $dependentIndexerIds = $this->dependencyInfoProvider->getIndexerIdsToRunAfter($this->indexer->getId()); foreach ($dependentIndexerIds as $indexerId) { - $this->indexerRegistry->get($indexerId)->reindexList($ids); + $dependentIndexer = $this->indexerRegistry->get($indexerId); + if (!$dependentIndexer->isScheduled()) { + $dependentIndexer->reindexList($ids); + } } } } diff --git a/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php b/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php new file mode 100644 index 0000000000000..8984b05365fb9 --- /dev/null +++ b/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * Interface to switch indexer mode + */ +interface ModeSwitcherInterface +{ + /** + * Returns data object that contains dimension modes + * + * @return DimensionModes + */ + public function getDimensionModes(): DimensionModes; + + /** + * Switch dimension mode + * + * @param string $currentMode + * @param string $previousMode + * @throws \InvalidArgumentException + * @throws \Zend_Db_Exception + * @return void + */ + public function switchMode(string $currentMode, string $previousMode); +} diff --git a/app/code/Magento/Indexer/Model/ProcessManager.php b/app/code/Magento/Indexer/Model/ProcessManager.php new file mode 100644 index 0000000000000..04cd713fffb11 --- /dev/null +++ b/app/code/Magento/Indexer/Model/ProcessManager.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * Provide functionality for executing user functions in multi-thread mode. + */ +class ProcessManager +{ + /** + * Threads count environment variable name + */ + const THREADS_COUNT = 'MAGE_INDEXER_THREADS_COUNT'; + + /** @var bool */ + private $failInChildProcess = false; + + /** @var \Magento\Framework\App\ResourceConnection */ + private $resource; + + /** @var \Magento\Framework\Registry */ + private $registry; + + /** @var int|null */ + private $threadsCount; + + /** + * @param \Magento\Framework\App\ResourceConnection $resource + * @param \Magento\Framework\Registry $registry + * @param int|null $threadsCount + */ + public function __construct( + \Magento\Framework\App\ResourceConnection $resource, + \Magento\Framework\Registry $registry = null, + int $threadsCount = null + ) { + $this->resource = $resource; + if (null === $registry) { + $registry = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\Registry::class + ); + } + $this->registry = $registry; + $this->threadsCount = (int)$threadsCount; + } + + /** + * Execute user functions + * + * @param \Traversable $userFunctions + */ + public function execute($userFunctions) + { + if ($this->threadsCount > 1 && $this->isCanBeParalleled() && !$this->isSetupMode() && PHP_SAPI == 'cli') { + $this->multiThreadsExecute($userFunctions); + } else { + $this->simpleThreadExecute($userFunctions); + } + } + + /** + * Execute user functions in singleThreads mode + * + * @param \Traversable $userFunctions + */ + private function simpleThreadExecute($userFunctions) + { + foreach ($userFunctions as $userFunction) { + call_user_func($userFunction); + } + } + + /** + * Execute user functions in multiThreads mode + * + * @param \Traversable $userFunctions + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + private function multiThreadsExecute($userFunctions) + { + $this->resource->closeConnection(null); + $threadNumber = 0; + foreach ($userFunctions as $userFunction) { + $pid = pcntl_fork(); + if ($pid == -1) { + throw new \RuntimeException('Unable to fork a new process'); + } elseif ($pid) { + $this->executeParentProcess($threadNumber); + } else { + $this->startChildProcess($userFunction); + } + } + while (pcntl_waitpid(0, $status) != -1) { + //Waiting for the completion of child processes + } + + if ($this->failInChildProcess) { + throw new \RuntimeException('Fail in child process'); + } + } + + /** + * Is process can be paralleled + * + * @return bool + */ + private function isCanBeParalleled(): bool + { + return function_exists('pcntl_fork'); + } + + /** + * Is setup mode + * + * @return bool + */ + private function isSetupMode(): bool + { + return $this->registry->registry('setup-mode-enabled') ?: false; + } + + /** + * Start child process + * + * @param callable $userFunction + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function startChildProcess(callable $userFunction) + { + $status = call_user_func($userFunction); + $status = is_integer($status) ? $status : 0; + exit($status); + } + + /** + * Execute parent process + * + * @param int $threadNumber + */ + private function executeParentProcess(int &$threadNumber) + { + $threadNumber++; + if ($threadNumber >= $this->threadsCount) { + pcntl_wait($status); + if (pcntl_wexitstatus($status) !== 0) { + $this->failInChildProcess = true; + } + $threadNumber--; + } + } +} diff --git a/app/code/Magento/Indexer/Test/Mftf/composer.json b/app/code/Magento/Indexer/Test/Mftf/composer.json deleted file mode 100644 index 487c2ac1abc85..0000000000000 --- a/app/code/Magento/Indexer/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-indexer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php new file mode 100644 index 0000000000000..3d913ee2860d2 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php @@ -0,0 +1,222 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Test\Unit\Console\Command; + +use Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Test for class \Magento\Indexer\Model\ModeSwitcherInterface. + */ +class IndexerSetDimensionsModeCommandTest extends AbstractIndexerCommandCommonSetup +{ + /** + * Command being tested + * + * @var IndexerSetDimensionsModeCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $command; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configReaderMock; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface[] + */ + private $dimensionProviders; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $dimensionModeSwitcherMock; + + /** + * @var \Magento\Indexer\Model\Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerMock; + + /** + * @var \Magento\Indexer\Model\DimensionModes|\PHPUnit_Framework_MockObject_MockObject + */ + private $dimensionModes; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->configReaderMock = $this->createMock(ScopeConfigInterface::class); + $this->dimensionModeSwitcherMock = + $this->createMock(\Magento\Indexer\Model\ModeSwitcherInterface::class); + $this->dimensionProviders = [ + 'indexer_title' => $this->dimensionModeSwitcherMock, + ]; + $this->dimensionModes = $this->createMock(\Magento\Indexer\Model\DimensionModes::class); + $this->command = $objectManagerHelper->getObject( + IndexerSetDimensionsModeCommand::class, + [ + 'objectManagerFactory' => $this->objectManagerFactory, + 'configReader' => $this->configReaderMock, + 'dimensionSwitchers' => $this->dimensionProviders, + ] + ); + } + + /** + * Get return value map for object manager + * + * @return array + */ + protected function getObjectManagerReturnValueMap() + { + $result = parent::getObjectManagerReturnValueMap(); + $this->indexerMock = $this->createMock(\Magento\Indexer\Model\Indexer::class); + $result[] = [\Magento\Indexer\Model\Indexer::class, $this->indexerMock]; + + return $result; + } + + /** + * Tests method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @param string $indexerTitle + * @param string $previousMode + * @param string $command + * @param string $consoleOutput + * @dataProvider dimensionModesDataProvider + * @return void + */ + public function testExecuteWithAttributes($indexerTitle, $previousMode, $command, $consoleOutput) + { + $this->configureAdminArea(); + $commandTester = new CommandTester($this->command); + $this->dimensionModes->method('getDimensions')->willReturn([ + $previousMode => 'dimension1', + $command['mode'] => 'dimension2', + ]); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $this->indexerMock->method('load')->willReturnSelf(); + $this->indexerMock->method('getTitle')->willReturn($indexerTitle); + $commandTester->execute($command); + $actualValue = $commandTester->getDisplay(); + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } + + /** + * @return array + */ + public function dimensionModesDataProvider(): array + { + return [ + 'was_changed' => [ + 'indexer_title' => 'indexer_title', + 'previousMode' => 'none', + 'command' => [ + 'indexer' => 'indexer_title', + 'mode' => 'store', + ], + 'output' => + sprintf( + 'Dimensions mode for indexer "%s" was changed from \'%s\' to \'%s\'', + 'indexer_title', + 'none', + 'store' + ) . PHP_EOL + , + ], + 'was_not_changed' => [ + 'indexer_title' => 'indexer_title', + 'previousMode' => 'none', + 'command' => [ + 'indexer' => 'indexer_title', + 'mode' => 'none', + ], + 'output' => + sprintf( + 'Dimensions mode for indexer "%s" has not been changed', + 'indexer_title' + ) . PHP_EOL + , + ], + ]; + } + + /** + * Tests indexer exception of method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage + * Invalid value for "<indexer>" argument. Accepted values for "<indexer>" are 'indexer_title' + * @return void + */ + public function testExecuteWithIndxerException() + { + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('getTitle')->willReturn('indexer_title'); + $commandTester->execute(['indexer' => 'non_existing_title']); + } + + /** + * Tests indexer exception of method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Missing argument "<mode>". Accepted values for "<mode>" are 'store,website' + * @return void + */ + public function testExecuteWithModeException() + { + $commandTester = new CommandTester($this->command); + $this->dimensionModes->method('getDimensions')->willReturn([ + 'store' => 'dimension1', + 'website' => 'dimension2', + ]); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $this->indexerMock->method('getTitle')->willReturn('indexer_title'); + $commandTester->execute([ + 'indexer' => 'indexer_title', + ]); + } + + /** + * Test execution of command without any arguments + * + * @return void + */ + public function testExecuteWithNoArguments() + { + $indexerTitle = 'indexer_title'; + $modesConfig = [ + 'store' => 'dimension1', + 'website' => 'dimension2', + ]; + $this->configureAdminArea(); + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('getTitle')->willReturn($indexerTitle); + $this->dimensionModes->method('getDimensions')->willReturn($modesConfig); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $commandTester->execute([]); + $actualValue = $commandTester->getDisplay(); + $consoleOutput = sprintf('%-50s', 'Indexer') . 'Available modes' . PHP_EOL + . sprintf('%-50s', $indexerTitle) . 'store,website' . PHP_EOL; + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php new file mode 100644 index 0000000000000..53a84b96b9713 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Test\Unit\Console\Command; + +use Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class IndexerShowDimensionsModeCommandTest extends AbstractIndexerCommandCommonSetup +{ + /** + * Command being tested + * + * @var IndexerShowDimensionsModeCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $command; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configReaderMock; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface[] + */ + private $indexers; + + /** + * @var \Magento\Indexer\Model\Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->configReaderMock = $this->createMock(ScopeConfigInterface::class); + $this->indexers = ['indexer_1' => 'indexer_1', 'indexer_2' => 'indexer_2']; + $this->command = $objectManagerHelper->getObject( + IndexerShowDimensionsModeCommand::class, + [ + 'objectManagerFactory' => $this->objectManagerFactory, + 'configReader' => $this->configReaderMock, + 'indexers' => $this->indexers, + ] + ); + } + + /** + * Get return value map for object manager + * + * @return array + */ + protected function getObjectManagerReturnValueMap(): array + { + $result = parent::getObjectManagerReturnValueMap(); + $this->indexerMock = $this->createMock(\Magento\Indexer\Model\Indexer::class); + $result[] = [\Magento\Indexer\Model\Indexer::class, $this->indexerMock]; + + return $result; + } + + /** + * Tests method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @param string $command + * @param string $consoleOutput + * @dataProvider dimensionModesDataProvider + */ + public function testExecuteWithAttributes($command, $consoleOutput) + { + $indexers = [['indexer_1'], ['indexer_2']]; + $indexerTitles = ['indexer_title1', 'indexer_title2']; + $this->configureAdminArea(); + /** @var CommandTester $commandTester */ + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('load')->withConsecutive(...$indexers); + $this->indexerMock->method('getTitle')->willReturnOnConsecutiveCalls(...$indexerTitles); + $commandTester->execute($command); + $actualValue = $commandTester->getDisplay(); + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } + + /** + * @return array + */ + public function dimensionModesDataProvider(): array + { + return [ + 'get_all' => [ + 'command' => [], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL . + sprintf( + '%-50s ', + 'indexer_title2' . ':' + ) . 'none' . PHP_EOL + , + ], + 'get_by_index' => [ + 'command' => [ + 'indexer' => ['indexer_1'], + ], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL + , + ], + 'get_by_several_indexes' => [ + 'command' => [ + 'indexer' => ['indexer_1', 'indexer_2'], + ], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL . + sprintf( + '%-50s ', + 'indexer_title2' . ':' + ) . 'none' . PHP_EOL + , + ], + ]; + } +} diff --git a/app/code/Magento/Indexer/etc/cron_groups.xml b/app/code/Magento/Indexer/etc/cron_groups.xml index 7afabee1949c0..076e73009ec21 100644 --- a/app/code/Magento/Indexer/etc/cron_groups.xml +++ b/app/code/Magento/Indexer/etc/cron_groups.xml @@ -11,8 +11,8 @@ <schedule_ahead_for>4</schedule_ahead_for> <schedule_lifetime>2</schedule_lifetime> <history_cleanup_every>10</history_cleanup_every> - <history_success_lifetime>10080</history_success_lifetime> - <history_failure_lifetime>10080</history_failure_lifetime> + <history_success_lifetime>60</history_success_lifetime> + <history_failure_lifetime>4320</history_failure_lifetime> <use_separate_process>1</use_separate_process> </group> </config> diff --git a/app/code/Magento/Indexer/etc/db_schema_whitelist.json b/app/code/Magento/Indexer/etc/db_schema_whitelist.json index e1b78c94b230a..8c9b55729a737 100644 --- a/app/code/Magento/Indexer/etc/db_schema_whitelist.json +++ b/app/code/Magento/Indexer/etc/db_schema_whitelist.json @@ -1,34 +1,34 @@ { - "indexer_state": { - "column": { - "state_id": true, - "indexer_id": true, - "status": true, - "updated": true, - "hash_config": true + "indexer_state": { + "column": { + "state_id": true, + "indexer_id": true, + "status": true, + "updated": true, + "hash_config": true + }, + "index": { + "INDEXER_STATE_INDEXER_ID": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "INDEXER_STATE_INDEXER_ID": true - }, - "constraint": { - "PRIMARY": true - } - }, - "mview_state": { - "column": { - "state_id": true, - "view_id": true, - "mode": true, - "status": true, - "updated": true, - "version_id": true - }, - "index": { - "MVIEW_STATE_VIEW_ID": true, - "MVIEW_STATE_MODE": true - }, - "constraint": { - "PRIMARY": true + "mview_state": { + "column": { + "state_id": true, + "view_id": true, + "mode": true, + "status": true, + "updated": true, + "version_id": true + }, + "index": { + "MVIEW_STATE_VIEW_ID": true, + "MVIEW_STATE_MODE": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index 610f08fac3a05..c7603191e8606 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -42,7 +42,11 @@ <plugin name="page-cache-indexer-reindex-clean-cache" type="Magento\Indexer\Model\Processor\CleanCache" sortOrder="10"/> </type> - + <type name="\Magento\Indexer\Model\ProcessManager"> + <arguments> + <argument name="threadsCount" xsi:type="init_parameter">Magento\Indexer\Model\ProcessManager::THREADS_COUNT</argument> + </arguments> + </type> <type name="Magento\Framework\Console\CommandListInterface"> <arguments> <argument name="commands" xsi:type="array"> @@ -52,6 +56,8 @@ <item name="show-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowModeCommand</item> <item name="status" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusCommand</item> <item name="reset" xsi:type="object">Magento\Indexer\Console\Command\IndexerResetStateCommand</item> + <item name="set-dimensions-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand</item> + <item name="show-dimensions-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand</item> </argument> </arguments> </type> diff --git a/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php b/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php index 0eb2afd04140a..d3e84083e5b42 100644 --- a/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php +++ b/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php @@ -11,7 +11,7 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Collect shipping rates for customer address without packaging estiamtion. + * Collect shipping rates for customer address without packaging estimation. */ class CarrierFinder { diff --git a/app/code/Magento/InstantPurchase/Test/Mftf/composer.json b/app/code/Magento/InstantPurchase/Test/Mftf/composer.json deleted file mode 100644 index 465002e5c4244..0000000000000 --- a/app/code/Magento/InstantPurchase/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-instant-purchase", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml b/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml index 4b7a6029507b4..76785c023ed0b 100644 --- a/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml +++ b/app/code/Magento/InstantPurchase/etc/adminhtml/system.xml @@ -10,7 +10,7 @@ <section id="sales"> <group id="instant_purchase" translate="label" type="text" sortOrder="200" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Instant Purchase</label> - <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="active" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Enabled</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>Payment method with vault and instant purchase support should be enabled.</comment> diff --git a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php index cc08796d98e28..d8684e635b943 100644 --- a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php +++ b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php @@ -78,7 +78,7 @@ public function getTabTitle() } /** - * Returns status flag about this tab can be showen or not + * Returns status flag about this tab can be shown or not * * @return true */ diff --git a/app/code/Magento/Integration/Controller/Token/Access.php b/app/code/Magento/Integration/Controller/Token/Access.php index 88d5029a724c7..5b75643f51b50 100644 --- a/app/code/Magento/Integration/Controller/Token/Access.php +++ b/app/code/Magento/Integration/Controller/Token/Access.php @@ -6,11 +6,15 @@ */ namespace Magento\Integration\Controller\Token; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Integration\Model\Integration as IntegrationModel; use Magento\Integration\Api\IntegrationServiceInterface as IntegrationService; use Magento\Integration\Api\OauthServiceInterface as IntegrationOauthService; +use Magento\Framework\App\Action\Action; -class Access extends \Magento\Framework\App\Action\Action +class Access extends Action implements CsrfAwareActionInterface { /** * @var \Magento\Framework\Oauth\OauthInterface @@ -53,6 +57,23 @@ public function __construct( $this->helper = $helper; } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Initiate AccessToken request operation * diff --git a/app/code/Magento/Integration/Controller/Token/Request.php b/app/code/Magento/Integration/Controller/Token/Request.php index 104a36e8ae8fe..04f368fc9f9fd 100644 --- a/app/code/Magento/Integration/Controller/Token/Request.php +++ b/app/code/Magento/Integration/Controller/Token/Request.php @@ -6,7 +6,12 @@ */ namespace Magento\Integration\Controller\Token; -class Request extends \Magento\Framework\App\Action\Action +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +class Request extends Action implements CsrfAwareActionInterface { /** * @var \Magento\Framework\Oauth\OauthInterface @@ -33,6 +38,23 @@ public function __construct( $this->helper = $helper; } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Initiate RequestToken request operation * diff --git a/app/code/Magento/Integration/Test/Mftf/composer.json b/app/code/Magento/Integration/Test/Mftf/composer.json deleted file mode 100644 index 7168fc1945dab..0000000000000 --- a/app/code/Magento/Integration/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-integration", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php b/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php index 7ca29e99d9ba9..7af2b3563478a 100644 --- a/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php +++ b/app/code/Magento/Integration/Test/Unit/Oauth/OauthTest.php @@ -629,7 +629,7 @@ public function testGetAccessTokenVerifierInvalid($verifier, $verifierFromToken) public function dataProviderForGetAccessTokenVerifierInvalidTest() { // Verifier is not a string - return [[3, 3], ['wrong_length', 'wrong_length'], ['verifier', 'doesnt match']]; + return [[3, 3], ['wrong_length', 'wrong_length'], ['verifier', 'doesn\'t match']]; } public function testGetAccessToken() diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml index 97aec083e7abb..5abec8efbfdd6 100644 --- a/app/code/Magento/Integration/etc/adminhtml/system.xml +++ b/app/code/Magento/Integration/etc/adminhtml/system.xml @@ -13,48 +13,48 @@ <resource>Magento_Integration::config_oauth</resource> <group id="access_token_lifetime" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Access Token Expiration</label> - <field id="customer" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="customer" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Customer Token Lifetime (hours)</label> <comment>We will disable this feature if the value is empty.</comment> </field> - <field id="admin" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="admin" translate="label comment" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Admin Token Lifetime (hours)</label> <comment>We will disable this feature if the value is empty.</comment> </field> </group> <group id="cleanup" translate="label" type="text" sortOrder="300" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Cleanup Settings</label> - <field id="cleanup_probability" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="cleanup_probability" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Cleanup Probability</label> <comment>Integer. Launch cleanup in X OAuth requests. 0 (not recommended) - to disable cleanup</comment> </field> - <field id="expiration_period" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="expiration_period" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Expiration Period</label> <comment>Cleanup entries older than X minutes.</comment> </field> </group> <group id="consumer" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Consumer Settings</label> - <field id="expiration_period" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="expiration_period" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Expiration Period</label> <comment>Consumer key/secret will expire if not used within X seconds after Oauth token exchange starts.</comment> </field> - <field id="post_maxredirects" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="post_maxredirects" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>OAuth consumer credentials HTTP Post maxredirects</label> <comment>Number of maximum redirects for OAuth consumer credentials Post request.</comment> </field> - <field id="post_timeout" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="post_timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>OAuth consumer credentials HTTP Post timeout</label> <comment>Timeout for OAuth consumer credentials Post request within X seconds.</comment> </field> </group> <group id="authentication_lock" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Authentication Locks</label> - <field id="max_failures_count" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="max_failures_count" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Maximum Login Failures to Lock Out Account</label> <comment>Maximum Number of authentication failures to lock out account.</comment> </field> - <field id="timeout" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="timeout" translate="label" type="text comment" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Lockout Time (seconds)</label> <comment>Period of time in seconds after which account will be unlocked.</comment> </field> diff --git a/app/code/Magento/Integration/etc/db_schema.xml b/app/code/Magento/Integration/etc/db_schema.xml index 0e95ef54e1b99..9e81b867d36d5 100644 --- a/app/code/Magento/Integration/etc/db_schema.xml +++ b/app/code/Magento/Integration/etc/db_schema.xml @@ -88,6 +88,9 @@ <column name="nonce"/> <column name="consumer_id"/> </constraint> + <index name="OAUTH_NONCE_TIMESTAMP" indexType="btree"> + <column name="timestamp"/> + </index> </table> <table name="integration" resource="default" engine="innodb" comment="integration"> <column xsi:type="int" name="integration_id" padding="10" unsigned="true" nullable="false" identity="true" diff --git a/app/code/Magento/Integration/etc/db_schema_whitelist.json b/app/code/Magento/Integration/etc/db_schema_whitelist.json index b7c3b992c0e27..a7649eeb4ee1e 100644 --- a/app/code/Magento/Integration/etc/db_schema_whitelist.json +++ b/app/code/Magento/Integration/etc/db_schema_whitelist.json @@ -1,94 +1,97 @@ { - "oauth_consumer": { - "column": { - "entity_id": true, - "created_at": true, - "updated_at": true, - "name": true, - "key": true, - "secret": true, - "callback_url": true, - "rejected_callback_url": true + "oauth_consumer": { + "column": { + "entity_id": true, + "created_at": true, + "updated_at": true, + "name": true, + "key": true, + "secret": true, + "callback_url": true, + "rejected_callback_url": true + }, + "index": { + "OAUTH_CONSUMER_CREATED_AT": true, + "OAUTH_CONSUMER_UPDATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "OAUTH_CONSUMER_KEY": true, + "OAUTH_CONSUMER_SECRET": true + } }, - "index": { - "OAUTH_CONSUMER_CREATED_AT": true, - "OAUTH_CONSUMER_UPDATED_AT": true + "oauth_token": { + "column": { + "entity_id": true, + "consumer_id": true, + "admin_id": true, + "customer_id": true, + "type": true, + "token": true, + "secret": true, + "verifier": true, + "callback_url": true, + "revoked": true, + "authorized": true, + "user_type": true, + "created_at": true + }, + "index": { + "OAUTH_TOKEN_CONSUMER_ID": true + }, + "constraint": { + "PRIMARY": true, + "OAUTH_TOKEN_ADMIN_ID_ADMIN_USER_USER_ID": true, + "OAUTH_TOKEN_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, + "OAUTH_TOKEN_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "OAUTH_TOKEN_TOKEN": true + } }, - "constraint": { - "PRIMARY": true, - "OAUTH_CONSUMER_KEY": true, - "OAUTH_CONSUMER_SECRET": true - } - }, - "oauth_token": { - "column": { - "entity_id": true, - "consumer_id": true, - "admin_id": true, - "customer_id": true, - "type": true, - "token": true, - "secret": true, - "verifier": true, - "callback_url": true, - "revoked": true, - "authorized": true, - "user_type": true, - "created_at": true - }, - "index": { - "OAUTH_TOKEN_CONSUMER_ID": true - }, - "constraint": { - "PRIMARY": true, - "OAUTH_TOKEN_ADMIN_ID_ADMIN_USER_USER_ID": true, - "OAUTH_TOKEN_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, - "OAUTH_TOKEN_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "OAUTH_TOKEN_TOKEN": true - } - }, - "oauth_nonce": { - "column": { - "nonce": true, - "timestamp": true, - "consumer_id": true + "oauth_nonce": { + "column": { + "nonce": true, + "timestamp": true, + "consumer_id": true + }, + "constraint": { + "OAUTH_NONCE_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, + "OAUTH_NONCE_NONCE_CONSUMER_ID": true + }, + "index": { + "OAUTH_NONCE_TIMESTAMP": true + } }, - "constraint": { - "OAUTH_NONCE_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, - "OAUTH_NONCE_NONCE_CONSUMER_ID": true - } - }, - "integration": { - "column": { - "integration_id": true, - "name": true, - "email": true, - "endpoint": true, - "status": true, - "consumer_id": true, - "created_at": true, - "updated_at": true, - "setup_type": true, - "identity_link_url": true - }, - "constraint": { - "PRIMARY": true, - "INTEGRATION_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, - "INTEGRATION_NAME": true, - "INTEGRATION_CONSUMER_ID": true - } - }, - "oauth_token_request_log": { - "column": { - "log_id": true, - "user_name": true, - "user_type": true, - "failures_count": true, - "lock_expires_at": true + "integration": { + "column": { + "integration_id": true, + "name": true, + "email": true, + "endpoint": true, + "status": true, + "consumer_id": true, + "created_at": true, + "updated_at": true, + "setup_type": true, + "identity_link_url": true + }, + "constraint": { + "PRIMARY": true, + "INTEGRATION_CONSUMER_ID_OAUTH_CONSUMER_ENTITY_ID": true, + "INTEGRATION_NAME": true, + "INTEGRATION_CONSUMER_ID": true + } }, - "constraint": { - "PRIMARY": true, - "OAUTH_TOKEN_REQUEST_LOG_USER_NAME_USER_TYPE": true + "oauth_token_request_log": { + "column": { + "log_id": true, + "user_name": true, + "user_type": true, + "failures_count": true, + "lock_expires_at": true + }, + "constraint": { + "PRIMARY": true, + "OAUTH_TOKEN_REQUEST_LOG_USER_NAME_USER_TYPE": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js index 0bd7df8c0fa10..6921f645a2330 100644 --- a/app/code/Magento/Integration/view/adminhtml/web/js/integration.js +++ b/app/code/Magento/Integration/view/adminhtml/web/js/integration.js @@ -200,7 +200,7 @@ define([ if (IdentityLogin.win.closed || IdentityLogin.win.location.href == IdentityLogin.successCallbackUrl //eslint-disable-line eqeqeq ) { - //Stop the the polling + //Stop the polling clearInterval(IdentityLogin.checker); $('body').trigger('processStart'); //Check for window closed diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json b/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json deleted file mode 100644 index 9d046b8c2a105..0000000000000 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-layered-navigation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml index e9bf7933b94a9..de4637847456e 100644 --- a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml +++ b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="catalog" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="catalog" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="layered_navigation" translate="label" sortOrder="490" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Layered Navigation</label> <field id="display_product_count" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/LayeredNavigation/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/LayeredNavigation/view/frontend/layout/catalogsearch_result_index.xml index ab9aa485aeea2..3bb21ed09a01c 100644 --- a/app/code/Magento/LayeredNavigation/view/frontend/layout/catalogsearch_result_index.xml +++ b/app/code/Magento/LayeredNavigation/view/frontend/layout/catalogsearch_result_index.xml @@ -7,6 +7,7 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> + <attribute name="class" value="page-with-filter"/> <referenceContainer name="sidebar.main"> <block class="Magento\LayeredNavigation\Block\Navigation\Search" name="catalogsearch.leftnav" before="-" template="Magento_LayeredNavigation::layer/view.phtml"> <block class="Magento\LayeredNavigation\Block\Navigation\State" name="catalogsearch.navigation.state" as="state" /> diff --git a/app/code/Magento/Marketplace/Test/Mftf/composer.json b/app/code/Magento/Marketplace/Test/Mftf/composer.json deleted file mode 100644 index 3d4fd741bc4ad..0000000000000 --- a/app/code/Magento/Marketplace/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-marketplace", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Marketplace/etc/adminhtml/menu.xml b/app/code/Magento/Marketplace/etc/adminhtml/menu.xml index 084ca708d5825..ae9e629f95364 100644 --- a/app/code/Magento/Marketplace/etc/adminhtml/menu.xml +++ b/app/code/Magento/Marketplace/etc/adminhtml/menu.xml @@ -7,7 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd"> <menu> - <add id="Magento_Marketplace::partners" title="Find Partners & Extensions" module="Magento_Marketplace" sortOrder="80" - action="marketplace/index" resource="Magento_Marketplace::partners"/> + <add id="Magento_Marketplace::partners" title="Find Partners & Extensions" translate="title" module="Magento_Marketplace" sortOrder="80" action="marketplace/index" resource="Magento_Marketplace::partners"/> </menu> </config> diff --git a/app/code/Magento/MediaStorage/Model/File/Storage/File.php b/app/code/Magento/MediaStorage/Model/File/Storage/File.php index 149d829e2acbe..ffbc2e3e90aad 100644 --- a/app/code/Magento/MediaStorage/Model/File/Storage/File.php +++ b/app/code/Magento/MediaStorage/Model/File/Storage/File.php @@ -286,11 +286,10 @@ public function saveDir($dir) */ public function saveFile($file, $overwrite = true) { - if (isset( - $file['filename'] - ) && !empty($file['filename']) && isset( - $file['content'] - ) && !empty($file['content']) + if (isset($file['filename']) + && !empty($file['filename']) + && isset($file['content']) + && !empty($file['content']) ) { try { $filename = isset( @@ -307,8 +306,6 @@ public function saveFile($file, $overwrite = true) } else { throw new \Magento\Framework\Exception\LocalizedException(__('Wrong file info format')); } - - return false; } /** diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php index 37dd6f485f1a8..6e3929296e252 100644 --- a/app/code/Magento/MediaStorage/Service/ImageResize.php +++ b/app/code/Magento/MediaStorage/Service/ImageResize.php @@ -202,7 +202,7 @@ private function getViewImages(array $themes): array ]); $images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE); foreach ($images as $imageId => $imageData) { - $uniqIndex = $this->getUniqImageIndex($imageData); + $uniqIndex = $this->getUniqueImageIndex($imageData); $imageData['id'] = $imageId; $viewImages[$uniqIndex] = $imageData; } @@ -211,11 +211,11 @@ private function getViewImages(array $themes): array } /** - * Get uniq image index + * Get unique image index * @param array $imageData * @return string */ - private function getUniqImageIndex(array $imageData): string + private function getUniqueImageIndex(array $imageData): string { ksort($imageData); unset($imageData['type']); diff --git a/app/code/Magento/MediaStorage/Test/Mftf/composer.json b/app/code/Magento/MediaStorage/Test/Mftf/composer.json deleted file mode 100644 index 7e7aeeef14517..0000000000000 --- a/app/code/Magento/MediaStorage/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-media-storage", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml index 09b6b23744053..d7244a5d4fd01 100644 --- a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="system" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="system" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="media_storage_configuration" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Storage Configuration for Media</label> <field id="media_storage" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> diff --git a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php index c4620862b2e10..f301fb9289efb 100644 --- a/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php +++ b/app/code/Magento/MessageQueue/Model/Cron/ConsumersRunner.php @@ -139,6 +139,8 @@ private function canBeRun($consumerName, array $allowedConsumers = []) */ private function getPidFilePath($consumerName) { - return $consumerName . static::PID_FILE_EXT; + $sanitizedHostname = preg_replace('/[^a-z0-9]/i', '', gethostname()); + + return $consumerName . '-' . $sanitizedHostname . static::PID_FILE_EXT; } } diff --git a/app/code/Magento/MessageQueue/Test/Mftf/composer.json b/app/code/Magento/MessageQueue/Test/Mftf/composer.json deleted file mode 100644 index e499fed390751..0000000000000 --- a/app/code/Magento/MessageQueue/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-message-queue", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php index 08c8f926522de..bf8796a03f52a 100644 --- a/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php +++ b/app/code/Magento/MessageQueue/Test/Unit/Model/Cron/ConsumersRunnerTest.php @@ -51,6 +51,8 @@ class ConsumersRunnerTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { + require_once __DIR__ . '/../../_files/consumers_runner_functions_mocks.php'; + $this->phpExecutableFinderMock = $this->getMockBuilder(phpExecutableFinder::class) ->disableOriginalConstructor() ->getMock(); @@ -116,7 +118,7 @@ public function testRun( $isRunExpects ) { $consumerName = 'consumerName'; - $pidFilePath = 'consumerName.pid'; + $pidFilePath = 'consumerName-myHostName.pid'; $this->deploymentConfigMock->expects($this->exactly(3)) ->method('get') @@ -164,7 +166,7 @@ public function runDataProvider() 'isRun' => false, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=20000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=20000'], 'allowedConsumers' => [], 'shellBackgroundExpects' => 1, 'isRunExpects' => 1, @@ -174,7 +176,7 @@ public function runDataProvider() 'isRun' => false, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => [], 'shellBackgroundExpects' => 1, 'isRunExpects' => 1, @@ -184,7 +186,7 @@ public function runDataProvider() 'isRun' => false, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => ['someConsumer'], 'shellBackgroundExpects' => 0, 'isRunExpects' => 0, @@ -194,7 +196,7 @@ public function runDataProvider() 'isRun' => true, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => ['someConsumer'], 'shellBackgroundExpects' => 0, 'isRunExpects' => 0, @@ -204,7 +206,7 @@ public function runDataProvider() 'isRun' => true, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => [], 'shellBackgroundExpects' => 0, 'isRunExpects' => 1, @@ -214,7 +216,7 @@ public function runDataProvider() 'isRun' => true, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => ['consumerName'], 'shellBackgroundExpects' => 0, 'isRunExpects' => 1, @@ -224,7 +226,7 @@ public function runDataProvider() 'isRun' => false, 'php' => '', 'command' => 'php '. BP . '/bin/magento queue:consumers:start %s %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid', '--max-messages=10000'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid', '--max-messages=10000'], 'allowedConsumers' => ['consumerName'], 'shellBackgroundExpects' => 1, 'isRunExpects' => 1, @@ -234,7 +236,7 @@ public function runDataProvider() 'isRun' => false, 'php' => '/bin/php', 'command' => '/bin/php '. BP . '/bin/magento queue:consumers:start %s %s', - 'arguments' => ['consumerName', '--pid-file-path=consumerName.pid'], + 'arguments' => ['consumerName', '--pid-file-path=consumerName-myHostName.pid'], 'allowedConsumers' => ['consumerName'], 'shellBackgroundExpects' => 1, 'isRunExpects' => 1, diff --git a/app/code/Magento/MessageQueue/Test/Unit/_files/consumers_runner_functions_mocks.php b/app/code/Magento/MessageQueue/Test/Unit/_files/consumers_runner_functions_mocks.php new file mode 100644 index 0000000000000..788a8464a0ce4 --- /dev/null +++ b/app/code/Magento/MessageQueue/Test/Unit/_files/consumers_runner_functions_mocks.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MessageQueue\Model\Cron; + +/** + * @return string + */ +function gethostname() +{ + return 'myHost@Name'; +} diff --git a/app/code/Magento/MessageQueue/composer.json b/app/code/Magento/MessageQueue/composer.json index 5cdf7351bbb61..9d3c77fa0184a 100644 --- a/app/code/Magento/MessageQueue/composer.json +++ b/app/code/Magento/MessageQueue/composer.json @@ -11,7 +11,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json index 14c30198e87e4..f31981d2ec40f 100644 --- a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json +++ b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json @@ -1,13 +1,13 @@ { - "queue_lock": { - "column": { - "id": true, - "message_code": true, - "created_at": true - }, - "constraint": { - "PRIMARY": true, - "QUEUE_LOCK_MESSAGE_CODE": true + "queue_lock": { + "column": { + "id": true, + "message_code": true, + "created_at": true + }, + "constraint": { + "PRIMARY": true, + "QUEUE_LOCK_MESSAGE_CODE": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Msrp/Test/Mftf/composer.json b/app/code/Magento/Msrp/Test/Mftf/composer.json deleted file mode 100644 index cd85cb6cb268a..0000000000000 --- a/app/code/Magento/Msrp/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-msrp", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-msrp-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Msrp/Test/Unit/Ui/DataProvider/Product/Listing/Collector/MsrpPriceTest.php b/app/code/Magento/Msrp/Test/Unit/Ui/DataProvider/Product/Listing/Collector/MsrpPriceTest.php index 90b060f51073f..adf2a5b4a0e9e 100644 --- a/app/code/Magento/Msrp/Test/Unit/Ui/DataProvider/Product/Listing/Collector/MsrpPriceTest.php +++ b/app/code/Magento/Msrp/Test/Unit/Ui/DataProvider/Product/Listing/Collector/MsrpPriceTest.php @@ -50,6 +50,9 @@ class MsrpPriceTest extends \PHPUnit\Framework\TestCase */ private $priceInfoExtensionFactory; + /** + * @return void + */ protected function setUp() { $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) @@ -86,6 +89,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testCollect() { $product = $this->getMockBuilder(Product::class) @@ -101,7 +107,7 @@ public function testCollect() \Magento\Catalog\Api\Data\ProductRender\PriceInfoExtensionInterface::class ) ->setMethods(['setMsrp']) - ->getMock(); + ->getMockForAbstractClass(); $priceInfo = $this->getMockBuilder(MsrpPriceInfoInterface::class) ->setMethods(['getPrice', 'getExtensionAttributes']) diff --git a/app/code/Magento/Multishipping/Controller/Checkout.php b/app/code/Magento/Multishipping/Controller/Checkout.php index 1870736a0efd9..92417c7cb3a18 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout.php +++ b/app/code/Magento/Multishipping/Controller/Checkout.php @@ -8,6 +8,7 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\App\RequestInterface; +use Magento\Framework\Exception\StateException; /** * Multishipping checkout controller @@ -84,6 +85,7 @@ protected function _getCheckoutSession() * * @param RequestInterface $request * @return \Magento\Framework\App\ResponseInterface + * @throws \Magento\Framework\Exception\NotFoundException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -152,7 +154,15 @@ public function dispatch(RequestInterface $request) return parent::dispatch($request); } - $quote = $this->_getCheckout()->getQuote(); + try { + $checkout = $this->_getCheckout(); + } catch (StateException $e) { + $this->getResponse()->setRedirect($this->_getHelper()->getMSNewShippingUrl()); + $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); + return parent::dispatch($request); + } + + $quote = $checkout->getQuote(); if (!$quote->hasItems() || $quote->getHasError() || $quote->isVirtual()) { $this->getResponse()->setRedirect($this->_getHelper()->getCartUrl()); $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); diff --git a/app/code/Magento/Multishipping/Controller/Checkout/CheckItems.php b/app/code/Magento/Multishipping/Controller/Checkout/CheckItems.php new file mode 100644 index 0000000000000..e9a46f1cf00f7 --- /dev/null +++ b/app/code/Magento/Multishipping/Controller/Checkout/CheckItems.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Multishipping\Controller\Checkout; + +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\App\Action\Context; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Multishipping\Controller\Checkout; +use Magento\Multishipping\Helper\Data as MultishippingHelper; +use Magento\Quote\Model\Quote\Item; +use Psr\Log\LoggerInterface; + +class CheckItems extends Checkout +{ + /** + * @var CheckoutSession + */ + private $checkoutSession; + + /** + * @var MultishippingHelper + */ + private $helper; + + /** + * @var Json + */ + private $json; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @param Context $context, + * @param CustomerSession $customerSession + * @param CustomerRepositoryInterface $customerRepository + * @param AccountManagementInterface $accountManagement + * @param CheckoutSession $checkoutSession + * @param MultishippingHelper $helper + * @param Json $json + * @param LoggerInterface $logger + */ + + public function __construct( + Context $context, + CustomerSession $customerSession, + CustomerRepositoryInterface $customerRepository, + AccountManagementInterface $accountManagement, + CheckoutSession $checkoutSession, + MultishippingHelper $helper, + Json $json, + LoggerInterface $logger + ) { + $this->checkoutSession = $checkoutSession; + $this->helper = $helper; + $this->json = $json; + $this->logger = $logger; + + parent::__construct( + $context, + $customerSession, + $customerRepository, + $accountManagement + ); + } + + /** + * @return void + */ + public function execute() + { + try { + $shippingInfo = $this->getRequest()->getPost('ship'); + if (!\is_array($shippingInfo)) { + throw new LocalizedException( + __('We are unable to process your request. Please, try again later.') + ); + } + + $itemsInfo = $this->collectItemsInfo($shippingInfo); + $totalQuantity = array_sum($itemsInfo); + + $maxQuantity = $this->helper->getMaximumQty(); + if ($totalQuantity > $maxQuantity) { + throw new LocalizedException( + __('Maximum qty allowed for Shipping to multiple addresses is %1', $maxQuantity) + ); + } + + $quote = $this->checkoutSession->getQuote(); + foreach ($quote->getAllItems() as $item) { + if (isset($itemsInfo[$item->getId()])) { + $this->updateItemQuantity($item, $itemsInfo[$item->getId()]); + } + } + + if ($quote->getHasError()) { + throw new LocalizedException(__($quote->getMessage())); + } + + $this->jsonResponse(); + } catch (LocalizedException $e) { + $this->jsonResponse($e->getMessage()); + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); + $this->jsonResponse('We are unable to process your request. Please, try again later.'); + } + } + + /** + * Updates quote item quantity. + * + * @param Item $item + * @param float $quantity + * @throws LocalizedException + */ + private function updateItemQuantity(Item $item, float $quantity) + { + if ($quantity > 0) { + $item->setQty($quantity); + if ($item->getHasError()) { + throw new LocalizedException(__($item->getMessage())); + } + } + } + + /** + * Group posted items. + * + * @param array $shippingInfo + * @return array + */ + private function collectItemsInfo(array $shippingInfo): array + { + $itemsInfo = []; + foreach ($shippingInfo as $itemData) { + if (!\is_array($itemData)) { + continue; + } + foreach ($itemData as $quoteItemId => $data) { + if (!isset($itemsInfo[$quoteItemId])) { + $itemsInfo[$quoteItemId] = 0; + } + $itemsInfo[$quoteItemId] += (double)$data['qty']; + } + } + + return $itemsInfo; + } + + /** + * JSON response builder. + * + * @param string $error + * @return void + */ + private function jsonResponse(string $error = '') + { + $this->getResponse()->representJson( + $this->json->serialize($this->getResponseData($error)) + ); + } + + /** + * Returns response data. + * + * @param string $error + * @return array + */ + private function getResponseData(string $error = ''): array + { + $response = [ + 'success' => true, + ]; + + if (!empty($error)) { + $response = [ + 'success' => false, + 'error_message' => $error, + ]; + } + + return $response; + } +} diff --git a/app/code/Magento/Multishipping/Helper/Data.php b/app/code/Magento/Multishipping/Helper/Data.php index c9b36e34cb304..c7f552ac9236a 100644 --- a/app/code/Magento/Multishipping/Helper/Data.php +++ b/app/code/Magento/Multishipping/Helper/Data.php @@ -11,16 +11,19 @@ */ class Data extends \Magento\Framework\App\Helper\AbstractHelper { - /**#@+ + /* * Xml paths for multishipping checkout + * **/ const XML_PATH_CHECKOUT_MULTIPLE_AVAILABLE = 'multishipping/options/checkout_multiple'; const XML_PATH_CHECKOUT_MULTIPLE_MAXIMUM_QUANTITY = 'multishipping/options/checkout_multiple_maximum_qty'; - /**#@-*/ - - /**#@-*/ + /** + * Checkout session + * + * @var \Magento\Checkout\Model\Session + */ protected $checkoutSession; /** diff --git a/app/code/Magento/Multishipping/Helper/Url.php b/app/code/Magento/Multishipping/Helper/Url.php index e293e3d4d7121..eaefa8fe8bee3 100644 --- a/app/code/Magento/Multishipping/Helper/Url.php +++ b/app/code/Magento/Multishipping/Helper/Url.php @@ -63,6 +63,16 @@ public function getMSShippingAddressSavedUrl() return $this->_getUrl('multishipping/checkout_address/shippingSaved'); } + /** + * Retrieve register url + * + * @return string + */ + public function getMSNewShippingUrl() + { + return $this->_getUrl('multishipping/checkout_address/newShipping'); + } + /** * Retrieve register url * diff --git a/app/code/Magento/Multishipping/Test/Mftf/composer.json b/app/code/Magento/Multishipping/Test/Mftf/composer.json deleted file mode 100644 index 344b2b1ef6525..0000000000000 --- a/app/code/Magento/Multishipping/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-multishipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Multishipping/etc/frontend/di.xml b/app/code/Magento/Multishipping/etc/frontend/di.xml index 5968e3324e309..0c2daaf45043e 100644 --- a/app/code/Magento/Multishipping/etc/frontend/di.xml +++ b/app/code/Magento/Multishipping/etc/frontend/di.xml @@ -43,6 +43,6 @@ <plugin name="multishipping_session_mapper" type="Magento\Multishipping\Model\Checkout\Type\Multishipping\Plugin" sortOrder="50" /> </type> <type name="Magento\Checkout\Controller\Cart"> - <plugin name="multishipping_clear_addresses" type="Magento\Multishipping\Model\Cart\Controller\CartPlugin" /> + <plugin name="multishipping_clear_addresses" type="Magento\Multishipping\Model\Cart\Controller\CartPlugin" sortOrder="50" /> </type> </config> diff --git a/app/code/Magento/Multishipping/i18n/en_US.csv b/app/code/Magento/Multishipping/i18n/en_US.csv index 43cc785c56eab..43615e697b931 100644 --- a/app/code/Magento/Multishipping/i18n/en_US.csv +++ b/app/code/Magento/Multishipping/i18n/en_US.csv @@ -89,4 +89,5 @@ Options,Options "Select Shipping Method","Select Shipping Method" "We received your order!","We received your order!" "Ship to:","Ship to:" -"Error:","Error:" \ No newline at end of file +"Error:","Error:" +"We are unable to process your request. Please, try again later.","We are unable to process your request. Please, try again later." diff --git a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml index 51e9e6352cfd5..c6bcdeb7b0413 100644 --- a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml +++ b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml @@ -9,7 +9,11 @@ <update handle="customer_form_template_handle"/> <body> <referenceContainer name="content"> - <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"/> + <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"> + <arguments> + <argument name="attribute_data" xsi:type="object">Magento\Customer\Block\DataProviders\AddressAttributeData</argument> + </arguments> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Multishipping/view/frontend/requirejs-config.js b/app/code/Magento/Multishipping/view/frontend/requirejs-config.js index f14159ba0a85a..0235d8b848154 100644 --- a/app/code/Magento/Multishipping/view/frontend/requirejs-config.js +++ b/app/code/Magento/Multishipping/view/frontend/requirejs-config.js @@ -9,7 +9,8 @@ var config = { multiShipping: 'Magento_Multishipping/js/multi-shipping', orderOverview: 'Magento_Multishipping/js/overview', payment: 'Magento_Multishipping/js/payment', - billingLoader: 'Magento_Checkout/js/checkout-loader' + billingLoader: 'Magento_Checkout/js/checkout-loader', + cartUpdate: 'Magento_Checkout/js/action/update-shopping-cart' } } }; diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml index a55b84c0a2d8a..0f96f7cdb708a 100644 --- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml +++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/addresses.phtml @@ -14,20 +14,42 @@ * @var $block \Magento\Multishipping\Block\Checkout\Addresses */ ?> -<form id="checkout_multishipping_form" data-mage-init='{"multiShipping":{}, "validation":{}}' action="<?= /* @escapeNotVerified */ $block->getPostActionUrl() ?>" method="post" class="multicheckout address form"> +<form id="checkout_multishipping_form" + data-mage-init='{ + "multiShipping":{}, + "validation":{}, + "cartUpdate": { + "validationURL": "/multishipping/checkout/checkItems", + "eventName": "updateMulticartItemQty" + }}' + action="<?= $block->escapeUrl($block->getPostActionUrl()) ?>" + method="post" + class="multicheckout address form"> +<form id="checkout_multishipping_form" + data-mage-init='{ + "multiShipping":{}, + "cartUpdate": { + "validationURL": "/multishipping/checkout/checkItems", + "eventName": "updateMulticartItemQty" + }}' + action="<?= $block->escapeUrl($block->getPostActionUrl()) ?>" + method="post" + class="multicheckout address form"> <div class="title"> - <strong><?= /* @escapeNotVerified */ __('Please select a shipping address for applicable items.') ?></strong> + <strong><?= $block->escapeHtml(__('Please select a shipping address for applicable items.')) ?></strong> </div> <input type="hidden" name="continue" value="0" id="can_continue_flag"/> <input type="hidden" name="new_address" value="0" id="add_new_address_flag"/> <div class="table-wrapper"> <table class="items data table" id="multiship-addresses-table"> - <caption class="table-caption"><?= /* @escapeNotVerified */ __('Please select a shipping address for applicable items.') ?></caption> + <caption class="table-caption"> + <?= $block->escapeHtml(__('Please select a shipping address for applicable items.')) ?> + </caption> <thead> <tr> - <th class="col product" scope="col"><?= /* @escapeNotVerified */ __('Product') ?></th> - <th class="col qty" scope="col"><?= /* @escapeNotVerified */ __('Qty') ?></th> - <th class="col address" scope="col"><?= /* @escapeNotVerified */ __('Send To') ?></th> + <th class="col product" scope="col"><?= $block->escapeHtml(__('Product')) ?></th> + <th class="col qty" scope="col"><?= $block->escapeHtml(__('Qty')) ?></th> + <th class="col address" scope="col"><?= $block->escapeHtml(__('Send To')) ?></th> <th class="col actions" scope="col"> </th> </tr> </thead> @@ -35,24 +57,37 @@ <?php foreach ($block->getItems() as $_index => $_item): ?> <?php if ($_item->getQuoteItem()) : ?> <tr> - <td class="col product" data-th="<?= $block->escapeHtml(__('Product')) ?>"><?= $block->getItemHtml($_item->getQuoteItem()) ?></td> + <td class="col product" data-th="<?= $block->escapeHtml(__('Product')) ?>"> + <?= $block->getItemHtml($_item->getQuoteItem()) ?> + </td> <td class="col qty" data-th="<?= $block->escapeHtml(__('Qty')) ?>"> <div class="field qty"> - <label for="ship-<?= /* @escapeNotVerified */ $_index ?>-<?= /* @escapeNotVerified */ $_item->getQuoteItemId() ?>-qty" class="label"> - <span><?= /* @escapeNotVerified */ __('Qty') ?></span> + <label for="ship-<?= $block->escapeHtml($_index) ?>-<?= $block->escapeHtml($_item->getQuoteItemId()) ?>-qty" + class="label"> + <span><?= $block->escapeHtml(__('Qty')) ?></span> </label> <div class="control"> - <input type="number" data-multiship-item-id="<?= /* @escapeNotVerified */ $_item->getSku() ?>" id="ship-<?= /* @escapeNotVerified */ $_index ?>-<?= /* @escapeNotVerified */ $_item->getQuoteItemId() ?>-qty" name="ship[<?= /* @escapeNotVerified */ $_index ?>][<?= /* @escapeNotVerified */ $_item->getQuoteItemId() ?>][qty]" value="<?= $block->escapeHtml($_item->getQty()) ?>" size="2" class="input-text qty" data-validate="{number: true}"/> + <input type="number" + data-multiship-item-id="<?= $block->escapeHtml($_item->getSku()) ?>" + id="ship-<?= $block->escapeHtml($_index) ?>-<?= $block->escapeHtml($_item->getQuoteItemId()) ?>-qty" + name="ship[<?= $block->escapeHtml($_index) ?>][<?= $block->escapeHtml($_item->getQuoteItemId()) ?>][qty]" + value="<?= $block->escapeHtml($_item->getQty()) ?>" + size="2" + class="input-text qty" + data-validate="{number: true}"/> </div> </div> </td> <td class="col address" data-th="<?= $block->escapeHtml(__('Send To')) ?>"> <?php if ($_item->getProduct()->getIsVirtual()): ?> - <div class="applicable"><?= /* @escapeNotVerified */ __('A shipping selection is not applicable.') ?></div> + <div class="applicable"> + <?= $block->escapeHtml(__('A shipping selection is not applicable.')) ?> + </div> <?php else: ?> <div class="field address"> - <label for="ship_<?= /* @escapeNotVerified */ $_index ?>_<?= /* @escapeNotVerified */ $_item->getQuoteItemId() ?>_address" class="label"> - <span><?= /* @escapeNotVerified */ __('Send To') ?></span> + <label for="ship_<?= $block->escapeHtml($_index) ?>_<?= $block->escapeHtml($_item->getQuoteItemId()) ?>_address" + class="label"> + <span><?= $block->escapeHtml(__('Send To')) ?></span> </label> <div class="control"> <?= $block->getAddressesHtmlSelect($_item, $_index) ?> @@ -61,8 +96,11 @@ <?php endif; ?> </td> <td class="col actions" data-th="<?= $block->escapeHtml(__('Actions')) ?>"> - <a href="<?= /* @escapeNotVerified */ $block->getItemDeleteUrl($_item) ?>" title="<?= /* @escapeNotVerified */ __('Remove Item') ?>" class="action delete" data-multiship-item-update=""> - <span><?= /* @escapeNotVerified */ __('Remove item') ?></span> + <a href="<?= $block->escapeUrl($block->getItemDeleteUrl($_item)) ?>" + title="<?= $block->escapeHtml(__('Remove Item')) ?>" + class="action delete" + data-multiship-item-remove=""> + <span><?= $block->escapeHtml(__('Remove item')) ?></span> </a> </td> </tr> @@ -73,12 +111,35 @@ </div> <div class="actions-toolbar"> <div class="primary"> - <button type="submit" title="<?= /* @escapeNotVerified */ __('Go to Shipping Information') ?>" class="action primary continue<?php if ($block->isContinueDisabled()):?> disabled<?php endif; ?>" data-role="can-continue" data-flag="1"<?php if ($block->isContinueDisabled()):?> disabled="disabled"<?php endif; ?>><span><?= /* @escapeNotVerified */ __('Go to Shipping Information') ?></span></button> + <button type="submit" + title="<?= $block->escapeHtml(__('Go to Shipping Information')) ?>" + class="action primary continue<?php if ($block->isContinueDisabled()):?> disabled<?php endif; ?>" + data-role="can-continue" + data-flag="1" + <?php if ($block->isContinueDisabled()):?> + disabled="disabled" + <?php endif; ?>> + <span><?= $block->escapeHtml(__('Go to Shipping Information')) ?></span> + </button> </div> <div class="secondary"> - <button type="submit" data-multiship-item-update="" class="action update" data-role="can-continue" data-flag="0"><span><?= /* @escapeNotVerified */ __('Update Qty & Addresses') ?></span></button> - <button type="button" title="<?= /* @escapeNotVerified */ __('Enter a New Address') ?>" class="action add" data-role="add-new-address"><span><?= /* @escapeNotVerified */ __('Enter a New Address') ?></span></button> - <a href="<?= /* @escapeNotVerified */ $block->getBackUrl() ?>" class="action back"><span><?= /* @escapeNotVerified */ __('Back to Shopping Cart') ?></span></a> + <button type="submit" + data-multiship-item-update="" + class="action update" + data-role="can-continue" + data-flag="0"> + <span><?= $block->escapeHtml(__('Update Qty & Addresses')) ?></span> + </button> + <button type="button" + title="<?= $block->escapeHtml(__('Enter a New Address')) ?>" + class="action add" + data-role="add-new-address"> + <span><?= $block->escapeHtml(__('Enter a New Address')) ?></span> + </button> + <a href="<?= $block->escapeUrl($block->getBackUrl()) ?>" + class="action back"> + <span><?= $block->escapeHtml(__('Back to Shopping Cart')) ?></span> + </a> </div> </div> </form> diff --git a/app/code/Magento/MysqlMq/Model/ResourceModel/Queue.php b/app/code/Magento/MysqlMq/Model/ResourceModel/Queue.php index c1cdd23be622c..d50ed851b64a9 100644 --- a/app/code/Magento/MysqlMq/Model/ResourceModel/Queue.php +++ b/app/code/Magento/MysqlMq/Model/ResourceModel/Queue.php @@ -151,7 +151,7 @@ public function getMessages($queueName, $limit = null) 'queue_message_status.status IN (?)', [QueueManagement::MESSAGE_STATUS_NEW, QueueManagement::MESSAGE_STATUS_RETRY_REQUIRED] )->where('queue.name = ?', $queueName) - ->order('queue_message_status.updated_at DESC'); + ->order('queue_message_status.updated_at ASC'); if ($limit) { $select->limit($limit); diff --git a/app/code/Magento/MysqlMq/Test/Mftf/composer.json b/app/code/Magento/MysqlMq/Test/Mftf/composer.json deleted file mode 100644 index 0aa70a28428e4..0000000000000 --- a/app/code/Magento/MysqlMq/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-mysql-mq", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/MysqlMq/Test/Unit/Model/ResourceModel/QueueTest.php b/app/code/Magento/MysqlMq/Test/Unit/Model/ResourceModel/QueueTest.php index 7f364054ec921..d3fe09a712945 100644 --- a/app/code/Magento/MysqlMq/Test/Unit/Model/ResourceModel/QueueTest.php +++ b/app/code/Magento/MysqlMq/Test/Unit/Model/ResourceModel/QueueTest.php @@ -206,7 +206,7 @@ public function testGetMessages() ] )->willReturnSelf(); $select->expects($this->once()) - ->method('order')->with('queue_message_status.updated_at DESC')->willReturnSelf(); + ->method('order')->with('queue_message_status.updated_at ASC')->willReturnSelf(); $select->expects($this->once())->method('limit')->with($limit)->willReturnSelf(); $connection->expects($this->once())->method('fetchAll')->with($select)->willReturn($messages); $this->assertEquals($messages, $this->queue->getMessages($queueName, $limit)); diff --git a/app/code/Magento/MysqlMq/composer.json b/app/code/Magento/MysqlMq/composer.json index 3d7ee4f8b037b..d9bcf307a5bf9 100644 --- a/app/code/Magento/MysqlMq/composer.json +++ b/app/code/Magento/MysqlMq/composer.json @@ -12,7 +12,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/MysqlMq/etc/db_schema_whitelist.json b/app/code/Magento/MysqlMq/etc/db_schema_whitelist.json index 4d5a919320fe9..711d19345f408 100644 --- a/app/code/Magento/MysqlMq/etc/db_schema_whitelist.json +++ b/app/code/Magento/MysqlMq/etc/db_schema_whitelist.json @@ -1,43 +1,43 @@ { - "queue": { - "column": { - "id": true, - "name": true + "queue": { + "column": { + "id": true, + "name": true + }, + "constraint": { + "PRIMARY": true, + "QUEUE_NAME": true + } }, - "constraint": { - "PRIMARY": true, - "QUEUE_NAME": true - } - }, - "queue_message": { - "column": { - "id": true, - "topic_name": true, - "body": true - }, - "constraint": { - "PRIMARY": true - } - }, - "queue_message_status": { - "column": { - "id": true, - "queue_id": true, - "message_id": true, - "updated_at": true, - "status": true, - "number_of_trials": true - }, - "index": { - "QUEUE_MESSAGE_STATUS_STATUS_UPDATED_AT": true + "queue_message": { + "column": { + "id": true, + "topic_name": true, + "body": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true, - "QUEUE_MESSAGE_STATUS_MESSAGE_ID_QUEUE_MESSAGE_ID": true, - "QUEUE_MESSAGE_ID_QUEUE_MESSAGE_STATUS_MESSAGE_ID": true, - "QUEUE_MESSAGE_STATUS_QUEUE_ID_QUEUE_ID": true, - "QUEUE_ID_QUEUE_MESSAGE_STATUS_QUEUE_ID": true, - "QUEUE_MESSAGE_STATUS_QUEUE_ID_MESSAGE_ID": true + "queue_message_status": { + "column": { + "id": true, + "queue_id": true, + "message_id": true, + "updated_at": true, + "status": true, + "number_of_trials": true + }, + "index": { + "QUEUE_MESSAGE_STATUS_STATUS_UPDATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "QUEUE_MESSAGE_STATUS_MESSAGE_ID_QUEUE_MESSAGE_ID": true, + "QUEUE_MESSAGE_ID_QUEUE_MESSAGE_STATUS_MESSAGE_ID": true, + "QUEUE_MESSAGE_STATUS_QUEUE_ID_QUEUE_ID": true, + "QUEUE_ID_QUEUE_MESSAGE_STATUS_QUEUE_ID": true, + "QUEUE_MESSAGE_STATUS_QUEUE_ID_MESSAGE_ID": true + } } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json b/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json deleted file mode 100644 index 287475d097084..0000000000000 --- a/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-new-relic-reporting", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/NewRelicReporting/etc/db_schema_whitelist.json b/app/code/Magento/NewRelicReporting/etc/db_schema_whitelist.json index f5bf0afbb3e61..aa59e6cf831ea 100644 --- a/app/code/Magento/NewRelicReporting/etc/db_schema_whitelist.json +++ b/app/code/Magento/NewRelicReporting/etc/db_schema_whitelist.json @@ -1,61 +1,61 @@ { - "reporting_counts": { - "column": { - "entity_id": true, - "type": true, - "count": true, - "updated_at": true + "reporting_counts": { + "column": { + "entity_id": true, + "type": true, + "count": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "reporting_module_status": { - "column": { - "entity_id": true, - "name": true, - "active": true, - "setup_version": true, - "state": true, - "updated_at": true + "reporting_module_status": { + "column": { + "entity_id": true, + "name": true, + "active": true, + "setup_version": true, + "state": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "reporting_orders": { - "column": { - "entity_id": true, - "customer_id": true, - "total": true, - "total_base": true, - "item_count": true, - "updated_at": true + "reporting_orders": { + "column": { + "entity_id": true, + "customer_id": true, + "total": true, + "total_base": true, + "item_count": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "reporting_users": { - "column": { - "entity_id": true, - "type": true, - "action": true, - "updated_at": true - }, - "constraint": { - "PRIMARY": true - } - }, - "reporting_system_updates": { - "column": { - "entity_id": true, - "type": true, - "action": true, - "updated_at": true + "reporting_users": { + "column": { + "entity_id": true, + "type": true, + "action": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true + "reporting_system_updates": { + "column": { + "entity_id": true, + "type": true, + "action": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php b/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php index c5f4dc68d4dd2..61a17d7ad5e51 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Problem.php @@ -19,7 +19,7 @@ class Problem extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'problem/list.phtml'; + protected $_template = 'Magento_Newsletter::problem/list.phtml'; /** * @var \Magento\Newsletter\Model\ResourceModel\Problem\Collection diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Queue/Edit.php b/app/code/Magento/Newsletter/Block/Adminhtml/Queue/Edit.php index f085b0f6c9c8b..ca90b5d84a10f 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Queue/Edit.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Queue/Edit.php @@ -19,7 +19,7 @@ class Edit extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'queue/edit.phtml'; + protected $_template = 'Magento_Newsletter::queue/edit.phtml'; /** * Core registry diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php b/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php index 86e7e7ee4756d..4d5165db68736 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Subscriber.php @@ -29,7 +29,7 @@ class Subscriber extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'subscriber/list.phtml'; + protected $_template = 'Magento_Newsletter::subscriber/list.phtml'; /** * @var \Magento\Newsletter\Model\ResourceModel\Queue\CollectionFactory diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Template.php b/app/code/Magento/Newsletter/Block/Adminhtml/Template.php index 02b60e049f254..92ae6e6c3db04 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Template.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Template.php @@ -16,7 +16,7 @@ class Template extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'template/list.phtml'; + protected $_template = 'Magento_Newsletter::template/list.phtml'; /** * @return $this diff --git a/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php b/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php index 79db83e7a49b8..236101745b98e 100644 --- a/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php +++ b/app/code/Magento/Newsletter/Block/Adminhtml/Template/Edit.php @@ -67,12 +67,6 @@ public function getModel() */ protected function _prepareLayout() { - // Load Wysiwyg on demand and Prepare layout -// $block = $this->getLayout()->getBlock('head'); -// if ($this->_wysiwygConfig->isEnabled() && $block) { -// $block->setCanLoadTinyMce(true); -// } - $this->getToolbar()->addChild( 'back_button', \Magento\Backend\Block\Widget\Button::class, @@ -216,7 +210,7 @@ public function getForm() } /** - * Return return template name for JS + * Return template name for JS * * @return string */ diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php index 0dd2e5c4b387f..7f02e4ea13445 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php @@ -6,8 +6,32 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Subscriber; -class MassDelete extends \Magento\Newsletter\Controller\Adminhtml\Subscriber +use Magento\Newsletter\Controller\Adminhtml\Subscriber; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\Framework\App\ObjectManager; + +class MassDelete extends Subscriber { + /** + * @var SubscriberFactory + */ + private $subscriberFactory; + + /** + * @param Context $context + * @param FileFactory $fileFactory + */ + public function __construct( + Context $context, + FileFactory $fileFactory, + SubscriberFactory $subscriberFactory = null + ) { + $this->subscriberFactory = $subscriberFactory ?: ObjectManager::getInstance()->get(SubscriberFactory::class); + parent::__construct($context, $fileFactory); + } + /** * Delete one or more subscribers action * @@ -21,9 +45,7 @@ public function execute() } else { try { foreach ($subscribersIds as $subscriberId) { - $subscriber = $this->_objectManager->create( - \Magento\Newsletter\Model\Subscriber::class - )->load( + $subscriber = $this->subscriberFactory->create()->load( $subscriberId ); $subscriber->delete(); diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php index f294d23f46ece..b61494f795905 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php @@ -6,8 +6,33 @@ */ namespace Magento\Newsletter\Controller\Adminhtml\Subscriber; -class MassUnsubscribe extends \Magento\Newsletter\Controller\Adminhtml\Subscriber +use Magento\Newsletter\Controller\Adminhtml\Subscriber; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\Framework\App\ObjectManager; + +class MassUnsubscribe extends Subscriber { + /** + * @var SubscriberFactory + */ + private $subscriberFactory; + + /** + * @param Context $context + * @param FileFactory $fileFactory + * @param SubscriberFactory $subscriberFactory + */ + public function __construct( + Context $context, + FileFactory $fileFactory, + SubscriberFactory $subscriberFactory = null + ) { + $this->subscriberFactory = $subscriberFactory ?: ObjectManager::getInstance()->get(SubscriberFactory::class); + parent::__construct($context, $fileFactory); + } + /** * Unsubscribe one or more subscribers action * @@ -21,9 +46,7 @@ public function execute() } else { try { foreach ($subscribersIds as $subscriberId) { - $subscriber = $this->_objectManager->create( - \Magento\Newsletter\Model\Subscriber::class - )->load( + $subscriber = $this->subscriberFactory->create()->load( $subscriberId ); $subscriber->unsubscribe(); diff --git a/app/code/Magento/Newsletter/Controller/Manage/Save.php b/app/code/Magento/Newsletter/Controller/Manage/Save.php index 75ef8b26f50a9..419cbac10ffd1 100644 --- a/app/code/Magento/Newsletter/Controller/Manage/Save.php +++ b/app/code/Magento/Newsletter/Controller/Manage/Save.php @@ -4,9 +4,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Newsletter\Controller\Manage; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; +use Magento\Newsletter\Model\Subscriber; class Save extends \Magento\Newsletter\Controller\Manage { @@ -74,13 +76,28 @@ public function execute() $customer = $this->customerRepository->getById($customerId); $storeId = $this->storeManager->getStore()->getId(); $customer->setStoreId($storeId); - $this->customerRepository->save($customer); - if ((boolean)$this->getRequest()->getParam('is_subscribed', false)) { - $this->subscriberFactory->create()->subscribeCustomerById($customerId); - $this->messageManager->addSuccess(__('We saved the subscription.')); + $isSubscribedState = $customer->getExtensionAttributes() + ->getIsSubscribed(); + $isSubscribedParam = (boolean)$this->getRequest() + ->getParam('is_subscribed', false); + if ($isSubscribedParam !== $isSubscribedState) { + $this->customerRepository->save($customer); + if ($isSubscribedParam) { + $subscribeModel = $this->subscriberFactory->create() + ->subscribeCustomerById($customerId); + $subscribeStatus = $subscribeModel->getStatus(); + if ($subscribeStatus == Subscriber::STATUS_SUBSCRIBED) { + $this->messageManager->addSuccess(__('We have saved your subscription.')); + } else { + $this->messageManager->addSuccess(__('A confirmation request has been sent.')); + } + } else { + $this->subscriberFactory->create() + ->unsubscribeCustomerById($customerId); + $this->messageManager->addSuccess(__('We have removed your newsletter subscription.')); + } } else { - $this->subscriberFactory->create()->unsubscribeCustomerById($customerId); - $this->messageManager->addSuccess(__('We removed the subscription.')); + $this->messageManager->addSuccess(__('We have updated your subscription.')); } } catch (\Exception $e) { $this->messageManager->addError(__('Something went wrong while saving your subscription.')); diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php index 13ab40665e591..fd2a61702e909 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php @@ -10,19 +10,32 @@ use Magento\Customer\Model\Session; use Magento\Customer\Model\Url as CustomerUrl; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; +use Magento\Framework\Validator\EmailAddress as EmailValidator; +use Magento\Newsletter\Controller\Subscriber as SubscriberController; +use Magento\Newsletter\Model\Subscriber; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Newsletter\Model\SubscriberFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class NewAction extends \Magento\Newsletter\Controller\Subscriber +class NewAction extends SubscriberController { /** * @var CustomerAccountManagement */ protected $customerAccountManagement; + /** + * @var EmailValidator + */ + private $emailValidator; + /** * Initialize dependencies. * @@ -32,6 +45,7 @@ class NewAction extends \Magento\Newsletter\Controller\Subscriber * @param StoreManagerInterface $storeManager * @param CustomerUrl $customerUrl * @param CustomerAccountManagement $customerAccountManagement + * @param EmailValidator $emailValidator */ public function __construct( Context $context, @@ -39,9 +53,11 @@ public function __construct( Session $customerSession, StoreManagerInterface $storeManager, CustomerUrl $customerUrl, - CustomerAccountManagement $customerAccountManagement + CustomerAccountManagement $customerAccountManagement, + EmailValidator $emailValidator = null ) { $this->customerAccountManagement = $customerAccountManagement; + $this->emailValidator = $emailValidator ?: ObjectManager::getInstance()->get(EmailValidator::class); parent::__construct( $context, $subscriberFactory, @@ -55,7 +71,7 @@ public function __construct( * Validates that the email address isn't being used by a different account. * * @param string $email - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateEmailAvailable($email) @@ -64,7 +80,7 @@ protected function validateEmailAvailable($email) if ($this->_customerSession->getCustomerDataObject()->getEmail() !== $email && !$this->customerAccountManagement->isEmailAvailable($email, $websiteId) ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('This email address is already assigned to another user.') ); } @@ -73,19 +89,19 @@ protected function validateEmailAvailable($email) /** * Validates that if the current user is a guest, that they can subscribe to a newsletter. * - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateGuestSubscription() { - if ($this->_objectManager->get(\Magento\Framework\App\Config\ScopeConfigInterface::class) + if ($this->_objectManager->get(ScopeConfigInterface::class) ->getValue( - \Magento\Newsletter\Model\Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG, + ScopeInterface::SCOPE_STORE ) != 1 && !$this->_customerSession->isLoggedIn() ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __( 'Sorry, but the administrator denied subscription for guests. Please <a href="%1">register</a>.', $this->_customerUrl->getRegisterUrl() @@ -98,20 +114,19 @@ protected function validateGuestSubscription() * Validates the format of the email address * * @param string $email - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateEmailFormat($email) { - if (!\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please enter a valid email address.')); + if (!$this->emailValidator->isValid($email)) { + throw new LocalizedException(__('Please enter a valid email address.')); } } /** * New subscription action * - * @throws \Magento\Framework\Exception\LocalizedException * @return void */ public function execute() @@ -126,28 +141,37 @@ public function execute() $subscriber = $this->_subscriberFactory->create()->loadByEmail($email); if ($subscriber->getId() - && $subscriber->getSubscriberStatus() == \Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED + && (int) $subscriber->getSubscriberStatus() === Subscriber::STATUS_SUBSCRIBED ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('This email address is already subscribed.') ); } - $status = $this->_subscriberFactory->create()->subscribe($email); - if ($status == \Magento\Newsletter\Model\Subscriber::STATUS_NOT_ACTIVE) { - $this->messageManager->addSuccess(__('The confirmation request has been sent.')); - } else { - $this->messageManager->addSuccess(__('Thank you for your subscription.')); - } - } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addException( + $status = (int) $this->_subscriberFactory->create()->subscribe($email); + $this->messageManager->addSuccessMessage($this->getSuccessMessage($status)); + } catch (LocalizedException $e) { + $this->messageManager->addExceptionMessage( $e, __('There was a problem with the subscription: %1', $e->getMessage()) ); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Something went wrong with the subscription.')); + $this->messageManager->addExceptionMessage($e, __('Something went wrong with the subscription.')); } } $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); } + + /** + * @param int $status + * @return Phrase + */ + private function getSuccessMessage(int $status): Phrase + { + if ($status === Subscriber::STATUS_NOT_ACTIVE) { + return __('The confirmation request has been sent.'); + } + + return __('Thank you for your subscription.'); + } } diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Problem/Collection.php b/app/code/Magento/Newsletter/Model/ResourceModel/Problem/Collection.php index c0c5dfef277b8..0d7ebd74f724f 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Problem/Collection.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Problem/Collection.php @@ -165,8 +165,8 @@ protected function _addCustomersData() $customerName = $this->_customerView->getCustomerName($customer); foreach ($problems as $problem) { $problem->setCustomerName($customerName) - ->setCustomerFirstName($customer->getFirstName()) - ->setCustomerLastName($customer->getLastName()); + ->setCustomerFirstName($customer->getFirstname()) + ->setCustomerLastName($customer->getLastname()); } } catch (NoSuchEntityException $e) { // do nothing if customer is not found by id diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php b/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php index 9e98998f7f6ec..189549d2a73d2 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Queue/Collection.php @@ -221,7 +221,7 @@ public function addOnlyForSendingFilter() [\Magento\Newsletter\Model\Queue::STATUS_SENDING, \Magento\Newsletter\Model\Queue::STATUS_NEVER] )->where( 'main_table.queue_start_at < ?', - $this->_date->gmtdate() + $this->_date->gmtDate() )->where( 'main_table.queue_start_at IS NOT NULL' ); diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php index c7ce4b2f2f11b..4e059f9fe79a0 100644 --- a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php @@ -48,6 +48,13 @@ class Subscriber extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $mathRandom; + /** + * Guest customer id + * + * @var int + */ + private $guestCustomerId = 0; + /** * Construct * @@ -136,22 +143,24 @@ public function loadByCustomerData(\Magento\Customer\Api\Data\CustomerInterface return $result; } - $select = $this->connection - ->select() - ->from($this->getMainTable()) - ->where('subscriber_email=:subscriber_email and store_id=:store_id'); - - $result = $this->connection - ->fetchRow( - $select, - [ - 'subscriber_email' => $customer->getEmail(), - 'store_id' => $customer->getStoreId() - ] - ); - - if ($result) { - return $result; + if ($customer->getId() === $this->guestCustomerId) { + $select = $this->connection + ->select() + ->from($this->getMainTable()) + ->where('subscriber_email=:subscriber_email and store_id=:store_id'); + + $result = $this->connection + ->fetchRow( + $select, + [ + 'subscriber_email' => $customer->getEmail(), + 'store_id' => $customer->getStoreId() + ] + ); + + if ($result) { + return $result; + } } return []; diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index dcb6341a7a8dd..27ee8197778ff 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -419,7 +419,6 @@ public function subscribe($email) self::XML_PATH_CONFIRMATION_FLAG, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) == 1 ? true : false; - $isOwnSubscribes = false; $isSubscribeOwnEmail = $this->_customerSession->isLoggedIn() && $this->_customerSession->getCustomerDataObject()->getEmail() == $email; @@ -428,13 +427,7 @@ public function subscribe($email) || $this->getStatus() == self::STATUS_NOT_ACTIVE ) { if ($isConfirmNeed === true) { - // if user subscribes own login email - confirmation is not needed - $isOwnSubscribes = $isSubscribeOwnEmail; - if ($isOwnSubscribes == true) { - $this->setStatus(self::STATUS_SUBSCRIBED); - } else { - $this->setStatus(self::STATUS_NOT_ACTIVE); - } + $this->setStatus(self::STATUS_NOT_ACTIVE); } else { $this->setStatus(self::STATUS_SUBSCRIBED); } @@ -460,9 +453,7 @@ public function subscribe($email) try { /* Save model before sending out email */ $this->save(); - if ($isConfirmNeed === true - && $isOwnSubscribes === false - ) { + if ($isConfirmNeed === true) { $this->sendConfirmationRequestEmail(); } else { $this->sendConfirmationSuccessEmail(); @@ -568,7 +559,9 @@ protected function _updateCustomerSubscription($customerId, $subscribe) ) { $status = self::STATUS_UNCONFIRMED; } elseif ($isConfirmNeed) { - $status = self::STATUS_NOT_ACTIVE; + if ($this->getStatus() != self::STATUS_SUBSCRIBED) { + $status = self::STATUS_NOT_ACTIVE; + } } } elseif (($this->getStatus() == self::STATUS_UNCONFIRMED) && ($customerData->getConfirmation() === null)) { $status = self::STATUS_SUBSCRIBED; diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml new file mode 100644 index 0000000000000..92d17e9d71e2f --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + + <!--Create an Account. Check Sign Up for Newsletter checkbox --> + <actionGroup name="StorefrontCreateNewAccountNewsletterChecked" extends="SignUpNewUserFromStorefrontActionGroup"> + <arguments> + <argument name="Customer"/> + </arguments> + <click selector="{{StorefrontCustomerCreateFormSection.signUpForNewsletter}}" stepKey="selectSignUpForNewsletterCheckbox" after="fillLastName"/> + <see stepKey="seeDescriptionNewsletter" userInput='You are subscribed to "General Subscription".' selector="{{CustomerMyAccountPage.DescriptionNewsletter}}" /> + </actionGroup> + + <!--Create an Account. Uncheck Sign Up for Newsletter checkbox --> + <actionGroup name="StorefrontCreateNewAccountNewsletterUnchecked" extends="SignUpNewUserFromStorefrontActionGroup"> + <arguments> + <argument name="Customer"/> + <argument name="Store"/> + </arguments> + <amOnPage stepKey="amOnStorefrontPage" url="{{Store.code}}"/> + <see stepKey="seeDescriptionNewsletter" userInput="You aren't subscribed to our newsletter." selector="{{CustomerMyAccountPage.DescriptionNewsletter}}" /> + <see stepKey="seeThankYouMessage" userInput="Thank you for registering with NewStore."/> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Data/NewsletterTemplateData.xml b/app/code/Magento/Newsletter/Test/Mftf/Data/NewsletterTemplateData.xml index 9719a892aa702..fe2deed9a279f 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Data/NewsletterTemplateData.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Data/NewsletterTemplateData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultNewsletter" type="cms_page"> <data key="name" unique="suffix">Test Newsletter Template</data> <data key="subject">Test Newsletter Subject</data> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Page/NewsletterTemplatePage.xml b/app/code/Magento/Newsletter/Test/Mftf/Page/NewsletterTemplatePage.xml index b27bc5eee9f4d..fa655fadab551 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Page/NewsletterTemplatePage.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Page/NewsletterTemplatePage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="NewsletterTemplateForm" url="/newsletter/template/new/" area="admin" module="Magento_Cms"> <section name="StorefrontNewsletterSection"/> <section name="StorefrontNewsletterSection"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml index ef897ef6421bb..d0dfd21cc2e1f 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/NewsletterTemplateSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="BasicFieldNewsletterSection"> <element name="templateName" type="input" selector="#code"/> <element name="templateSubject" type="input" selector="#subject"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterSection.xml index 01d3a36bf29c5..ed2f5c316055c 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontNewsletterSection"> <element name="mediaDescription" type="text" selector="body>p>img" /> <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml new file mode 100644 index 0000000000000..06f762900436e --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + + <section name="StorefrontCustomerCreateFormSection"> + <element name="signUpForNewsletter" type="checkbox" selector="//span[contains(text(), 'Sign Up for Newsletter')]"/> + </section> + + <section name="CustomerMyAccountPage"> + <element name="DescriptionNewsletter" type="text" selector=".box-newsletter p"/> + </section> + +</sections> \ No newline at end of file diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml index f397b84dfaeb1..f69f94dbd79e3 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddImageToWYSIWYGNewsletterTest"> <annotations> <features value="Newsletter"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml index 353d33439f079..e3d73fb57333e 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddVariableToWYSIWYGNewsletterTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddVariableToWYSIWYGNewsletterTest"> <annotations> <features value="Newsletter"/> @@ -15,6 +15,7 @@ <title value="Admin should be able to add variable to WYSIWYG Editor of Newsletter"/> <description value="Admin should be able to add variable to WYSIWYG Editor Newsletter"/> <testCaseId value="MAGETWO-84379"/> + <severity value="AVERAGE"/> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="loginGetFromGeneralFile"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml index a447acc1b0fca..50a6b74a67233 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddWidgetToWYSIWYGNewsletterTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddWidgetToWYSIWYGNewsletterTest"> <annotations> <features value="Newsletter"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml new file mode 100644 index 0000000000000..faed8b1af952e --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="VerifySubscribedNewsletterDisplayedTest"> + <annotations> + <features value="Newsletter"/> + <stories value="MAGETWO-91701: Newsletter subscription is not correctly updated when user is registered on 2 stores"/> + <group value="Newsletter"/> + <title value="Newsletter subscription when user is registered on 2 stores"/> + <description value="Newsletter subscription when user is registered on 2 stores"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93836"/> + </annotations> + + <before> + <!--Log in to Magento as admin.--> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="Second"/> + <argument name="websiteCode" value="Base2"/> + </actionGroup> + + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createNewStore"> + <argument name="website" value="Second"/> + <argument name="storeGroupName" value="NewStore"/> + <argument name="storeGroupCode" value="Base12"/> + </actionGroup> + + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createCustomStoreView"> + <argument name="StoreGroup" value="staticStoreGroup"/> + <argument name="customStore" value="staticStore"/> + </actionGroup> + <actionGroup ref="EnableWebUrlOptions" stepKey="addStoreCodeToUrls"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + </before> + <after> + <!--Delete created data and set Default Configuration--> + <actionGroup ref="ResetWebUrlOptions" stepKey="resetUrlOption"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="Second"/> + </actionGroup> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!--Go to store front (default) and click Create an Account.--> + <actionGroup ref="StorefrontCreateNewAccountNewsletterChecked" stepKey="SignUpNewUser"> + <argument name="Customer" value="CustomerEntityOne"/> + </actionGroup> + <!--Sign Out--> + <amOnPage url="customer/account/logout/" stepKey="customerOnLogoutPage"/> + <waitForPageLoad stepKey="waitLogoutCustomer"/> + <!--Create new Account with the same email address. (unchecked Sign Up for Newsletter checkbox)--> + <actionGroup ref="StorefrontCreateNewAccountNewsletterUnchecked" stepKey="createNewAccountNewsletterUnchecked"> + <argument name="Customer" value="CustomerEntityOne"/> + <argument name="Store" value="staticStore"/> + </actionGroup> + </test> +</tests> + diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml index 69ea5677a09e8..3c19a3fa99d3c 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifyTinyMCEv4IsNativeWYSIWYGOnNewsletterTest"> <annotations> <features value="Newsletter"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/composer.json b/app/code/Magento/Newsletter/Test/Mftf/composer.json deleted file mode 100644 index 2a2ab444dd08a..0000000000000 --- a/app/code/Magento/Newsletter/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-newsletter", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php new file mode 100644 index 0000000000000..889fc11d71d7e --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php @@ -0,0 +1,199 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Test\Unit\Model; + +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\Model\Context; +use Magento\Framework\Registry; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Newsletter\Model\Problem as ProblemModel; +use Magento\Newsletter\Model\Queue; +use Magento\Newsletter\Model\ResourceModel\Problem as ProblemResource; +use Magento\Newsletter\Model\Subscriber; +use Magento\Newsletter\Model\SubscriberFactory; + +class ProblemTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var Registry|\PHPUnit_Framework_MockObject_MockObject + */ + private $registryMock; + + /** + * @var SubscriberFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriberFactoryMock; + + /** + * @var Subscriber|\PHPUnit_Framework_MockObject_MockObject + */ + private $subscriberMock; + + /** + * @var ProblemResource|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceModelMock; + + /** + * @var AbstractDb|\PHPUnit_Framework_MockObject_MockObject + */ + private $abstractDbMock; + + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @var ProblemModel + */ + private $problemModel; + + protected function setUp() + { + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->registryMock = $this->getMockBuilder(Registry::class) + ->disableOriginalConstructor() + ->getMock(); + $this->subscriberFactoryMock = $this->getMockBuilder(SubscriberFactory::class) + ->getMock(); + $this->subscriberMock = $this->getMockBuilder(Subscriber::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceModelMock = $this->getMockBuilder(ProblemResource::class) + ->disableOriginalConstructor() + ->getMock(); + $this->abstractDbMock = $this->getMockBuilder(AbstractDb::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->resourceModelMock->expects($this->any()) + ->method('getIdFieldName') + ->willReturn('id'); + + $this->objectManager = new ObjectManager($this); + + $this->problemModel = $this->objectManager->getObject( + ProblemModel::class, + [ + 'context' => $this->contextMock, + 'registry' => $this->registryMock, + 'subscriberFactory' => $this->subscriberFactoryMock, + 'resource' => $this->resourceModelMock, + 'resourceCollection' => $this->abstractDbMock, + 'data' => [], + ] + ); + } + + public function testAddSubscriberData() + { + $subscriberId = 1; + $this->subscriberMock->expects($this->once()) + ->method('getId') + ->willReturn($subscriberId); + + $result = $this->problemModel->addSubscriberData($this->subscriberMock); + + self::assertEquals($result, $this->problemModel); + self::assertEquals($subscriberId, $this->problemModel->getSubscriberId()); + } + + public function testAddQueueData() + { + $queueId = 1; + $queueMock = $this->getMockBuilder(Queue::class) + ->disableOriginalConstructor() + ->getMock(); + $queueMock->expects($this->once()) + ->method('getId') + ->willReturn($queueId); + + $result = $this->problemModel->addQueueData($queueMock); + + self::assertEquals($result, $this->problemModel); + self::assertEquals($queueId, $this->problemModel->getQueueId()); + } + + public function testAddErrorData() + { + $exceptionMessage = 'Some message'; + $exceptionCode = 111; + $exception = new \Exception($exceptionMessage, $exceptionCode); + + $result = $this->problemModel->addErrorData($exception); + + self::assertEquals($result, $this->problemModel); + self::assertEquals($exceptionMessage, $this->problemModel->getProblemErrorText()); + self::assertEquals($exceptionCode, $this->problemModel->getProblemErrorCode()); + } + + public function testGetSubscriberWithNoSubscriberId() + { + self::assertNull($this->problemModel->getSubscriber()); + } + + public function testGetSubscriber() + { + $this->setSubscriber(); + self::assertEquals($this->subscriberMock, $this->problemModel->getSubscriber()); + } + + public function testUnsubscribeWithNoSubscriber() + { + $this->subscriberMock->expects($this->never()) + ->method('__call') + ->with($this->equalTo('setSubscriberStatus')); + + $result = $this->problemModel->unsubscribe(); + + self::assertEquals($this->problemModel, $result); + } + + public function testUnsubscribe() + { + $this->setSubscriber(); + $this->subscriberMock->expects($this->at(1)) + ->method('__call') + ->with($this->equalTo('setSubscriberStatus'), $this->equalTo([Subscriber::STATUS_UNSUBSCRIBED])) + ->willReturnSelf(); + $this->subscriberMock->expects($this->at(2)) + ->method('__call') + ->with($this->equalTo('setIsStatusChanged')) + ->willReturnSelf(); + $this->subscriberMock->expects($this->once()) + ->method('save'); + + $result = $this->problemModel->unsubscribe(); + + self::assertEquals($this->problemModel, $result); + } + + /** + * Sets subscriber to the Problem model + */ + private function setSubscriber() + { + $subscriberId = 1; + $this->problemModel->setSubscriberId($subscriberId); + $this->subscriberFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->subscriberMock); + $this->subscriberMock->expects($this->once()) + ->method('load') + ->with($subscriberId) + ->willReturnSelf(); + } +} diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php index 86d40187de845..e8b141a24c9e8 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/Queue/TransportBuilderTest.php @@ -45,6 +45,11 @@ class TransportBuilderTest extends \PHPUnit\Framework\TestCase */ protected $mailTransportFactoryMock; + /** + * @var \Magento\Framework\Mail\MessageInterfaceFactory | \PHPUnit_Framework_MockObject_MockObject + */ + private $messageFactoryMock; + /** * @return void */ @@ -52,7 +57,10 @@ public function setUp() { $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->templateFactoryMock = $this->createMock(\Magento\Framework\Mail\Template\FactoryInterface::class); - $this->messageMock = $this->createMock(\Magento\Framework\Mail\Message::class); + $this->messageMock = $this->getMockBuilder(\Magento\Framework\Mail\MessageInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setBodyHtml', 'setSubject']) + ->getMockForAbstractClass(); $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); $this->senderResolverMock = $this->createMock(\Magento\Framework\Mail\Template\SenderResolverInterface::class); $this->mailTransportFactoryMock = $this->getMockBuilder( @@ -60,6 +68,11 @@ public function setUp() )->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); + $this->messageFactoryMock = $this->getMockBuilder(\Magento\Framework\Mail\MessageInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->messageFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($this->messageMock); $this->builder = $objectManagerHelper->getObject( $this->builderClassName, [ @@ -67,7 +80,8 @@ public function setUp() 'message' => $this->messageMock, 'objectManager' => $this->objectManagerMock, 'senderResolver' => $this->senderResolverMock, - 'mailTransportFactory' => $this->mailTransportFactoryMock + 'mailTransportFactory' => $this->mailTransportFactoryMock, + 'messageFactory' => $this->messageFactoryMock ] ); } @@ -108,7 +122,7 @@ public function testGetTransport( $template->expects($this->once()) ->method('getProcessedTemplate') ->with($vars) - ->will($this->returnValue($bodyText)); + ->willReturn($bodyText); $template->expects($this->once()) ->method('setTemplateFilter') ->with($filter); @@ -123,46 +137,8 @@ public function testGetTransport( $this->returnValue($template) ); - $this->messageMock->expects( - $this->once() - )->method( - 'setSubject' - )->with( - $this->equalTo('Email Subject') - )->will( - $this->returnSelf() - ); - $this->messageMock->expects( - $this->once() - )->method( - 'setBodyHtml' - )->with( - $this->equalTo($bodyText) - )->will( - $this->returnSelf() - ); - - $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); - - $this->mailTransportFactoryMock->expects( - $this->at(0) - )->method( - 'create' - )->with( - $this->equalTo(['message' => $this->messageMock]) - )->will( - $this->returnValue($transport) - ); - - $this->objectManagerMock->expects( - $this->at(0) - )->method( - 'create' - )->with( - $this->equalTo(\Magento\Framework\Mail\Message::class) - )->will( - $this->returnValue($transport) - ); + $this->messageMock->expects($this->once())->method('setBodyHtml')->willReturnSelf(); + $this->messageMock->expects($this->once())->method('setSubject')->willReturnSelf(); $this->builder->setTemplateIdentifier( 'identifier' @@ -174,8 +150,6 @@ public function testGetTransport( $data ); - $result = $this->builder->getTransport(); - - $this->assertInstanceOf(\Magento\Framework\Mail\TransportInterface::class, $result); + $this->builder->getTransport(); } } diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php index 39beade9346d4..1318cb1f98f58 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Newsletter\Test\Unit\Model; +use Magento\Newsletter\Model\Subscriber; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -116,7 +118,7 @@ public function testSubscribe() $email = 'subscriber_email@magento.com'; $this->resource->expects($this->any())->method('loadByEmail')->willReturn( [ - 'subscriber_status' => 3, + 'subscriber_status' => Subscriber::STATUS_UNSUBSCRIBED, 'subscriber_email' => $email, 'name' => 'subscriber_name' ] @@ -133,7 +135,7 @@ public function testSubscribe() $this->sendEmailCheck(); $this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf(); - $this->assertEquals(1, $this->subscriber->subscribe($email)); + $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->subscribe($email)); } public function testSubscribeNotLoggedIn() @@ -141,7 +143,7 @@ public function testSubscribeNotLoggedIn() $email = 'subscriber_email@magento.com'; $this->resource->expects($this->any())->method('loadByEmail')->willReturn( [ - 'subscriber_status' => 3, + 'subscriber_status' => Subscriber::STATUS_UNSUBSCRIBED, 'subscriber_email' => $email, 'name' => 'subscriber_name' ] @@ -158,7 +160,7 @@ public function testSubscribeNotLoggedIn() $this->sendEmailCheck(); $this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf(); - $this->assertEquals(2, $this->subscriber->subscribe($email)); + $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->subscribe($email)); } public function testUpdateSubscription() @@ -175,7 +177,7 @@ public function testUpdateSubscription() ->willReturn( [ 'subscriber_id' => 1, - 'subscriber_status' => 1 + 'subscriber_status' => Subscriber::STATUS_SUBSCRIBED ] ); $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); @@ -210,7 +212,7 @@ public function testUnsubscribeCustomerById() ->willReturn( [ 'subscriber_id' => 1, - 'subscriber_status' => 1 + 'subscriber_status' => Subscriber::STATUS_SUBSCRIBED ] ); $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); @@ -236,7 +238,7 @@ public function testSubscribeCustomerById() ->willReturn( [ 'subscriber_id' => 1, - 'subscriber_status' => 3 + 'subscriber_status' => Subscriber::STATUS_UNSUBSCRIBED ] ); $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); @@ -262,7 +264,7 @@ public function testSubscribeCustomerById1() ->willReturn( [ 'subscriber_id' => 1, - 'subscriber_status' => 3 + 'subscriber_status' => Subscriber::STATUS_UNSUBSCRIBED ] ); $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); @@ -276,7 +278,7 @@ public function testSubscribeCustomerById1() $this->scopeConfig->expects($this->atLeastOnce())->method('getValue')->with()->willReturn(true); $this->subscriber->subscribeCustomerById($customerId); - $this->assertEquals(\Magento\Newsletter\Model\Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->getStatus()); + $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->subscriber->getStatus()); } public function testSubscribeCustomerByIdAfterConfirmation() @@ -293,7 +295,7 @@ public function testSubscribeCustomerByIdAfterConfirmation() ->willReturn( [ 'subscriber_id' => 1, - 'subscriber_status' => 4 + 'subscriber_status' => Subscriber::STATUS_UNCONFIRMED ] ); $customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id'); @@ -305,7 +307,7 @@ public function testSubscribeCustomerByIdAfterConfirmation() $this->scopeConfig->expects($this->atLeastOnce())->method('getValue')->with()->willReturn(true); $this->subscriber->updateSubscription($customerId); - $this->assertEquals(\Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED, $this->subscriber->getStatus()); + $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $this->subscriber->getStatus()); } public function testUnsubscribe() diff --git a/app/code/Magento/Newsletter/etc/db_schema_whitelist.json b/app/code/Magento/Newsletter/etc/db_schema_whitelist.json index 27a42a55a174c..27666096e426e 100644 --- a/app/code/Magento/Newsletter/etc/db_schema_whitelist.json +++ b/app/code/Magento/Newsletter/etc/db_schema_whitelist.json @@ -1,116 +1,116 @@ { - "newsletter_subscriber": { - "column": { - "subscriber_id": true, - "store_id": true, - "change_status_at": true, - "customer_id": true, - "subscriber_email": true, - "subscriber_status": true, - "subscriber_confirm_code": true + "newsletter_subscriber": { + "column": { + "subscriber_id": true, + "store_id": true, + "change_status_at": true, + "customer_id": true, + "subscriber_email": true, + "subscriber_status": true, + "subscriber_confirm_code": true + }, + "index": { + "NEWSLETTER_SUBSCRIBER_CUSTOMER_ID": true, + "NEWSLETTER_SUBSCRIBER_STORE_ID": true, + "NEWSLETTER_SUBSCRIBER_SUBSCRIBER_EMAIL": true + }, + "constraint": { + "PRIMARY": true, + "NEWSLETTER_SUBSCRIBER_STORE_ID_STORE_STORE_ID": true + } }, - "index": { - "NEWSLETTER_SUBSCRIBER_CUSTOMER_ID": true, - "NEWSLETTER_SUBSCRIBER_STORE_ID": true, - "NEWSLETTER_SUBSCRIBER_SUBSCRIBER_EMAIL": true + "newsletter_template": { + "column": { + "template_id": true, + "template_code": true, + "template_text": true, + "template_styles": true, + "template_type": true, + "template_subject": true, + "template_sender_name": true, + "template_sender_email": true, + "template_actual": true, + "added_at": true, + "modified_at": true + }, + "index": { + "NEWSLETTER_TEMPLATE_TEMPLATE_ACTUAL": true, + "NEWSLETTER_TEMPLATE_ADDED_AT": true, + "NEWSLETTER_TEMPLATE_MODIFIED_AT": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true, - "NEWSLETTER_SUBSCRIBER_STORE_ID_STORE_STORE_ID": true - } - }, - "newsletter_template": { - "column": { - "template_id": true, - "template_code": true, - "template_text": true, - "template_styles": true, - "template_type": true, - "template_subject": true, - "template_sender_name": true, - "template_sender_email": true, - "template_actual": true, - "added_at": true, - "modified_at": true - }, - "index": { - "NEWSLETTER_TEMPLATE_TEMPLATE_ACTUAL": true, - "NEWSLETTER_TEMPLATE_ADDED_AT": true, - "NEWSLETTER_TEMPLATE_MODIFIED_AT": true - }, - "constraint": { - "PRIMARY": true - } - }, - "newsletter_queue": { - "column": { - "queue_id": true, - "template_id": true, - "newsletter_type": true, - "newsletter_text": true, - "newsletter_styles": true, - "newsletter_subject": true, - "newsletter_sender_name": true, - "newsletter_sender_email": true, - "queue_status": true, - "queue_start_at": true, - "queue_finish_at": true - }, - "index": { - "NEWSLETTER_QUEUE_TEMPLATE_ID": true - }, - "constraint": { - "PRIMARY": true, - "NEWSLETTER_QUEUE_TEMPLATE_ID_NEWSLETTER_TEMPLATE_TEMPLATE_ID": true - } - }, - "newsletter_queue_link": { - "column": { - "queue_link_id": true, - "queue_id": true, - "subscriber_id": true, - "letter_sent_at": true - }, - "index": { - "NEWSLETTER_QUEUE_LINK_SUBSCRIBER_ID": true, - "NEWSLETTER_QUEUE_LINK_QUEUE_ID_LETTER_SENT_AT": true - }, - "constraint": { - "PRIMARY": true, - "NEWSLETTER_QUEUE_LINK_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, - "NLTTR_QUEUE_LNK_SUBSCRIBER_ID_NLTTR_SUBSCRIBER_SUBSCRIBER_ID": true - } - }, - "newsletter_queue_store_link": { - "column": { - "queue_id": true, - "store_id": true - }, - "index": { - "NEWSLETTER_QUEUE_STORE_LINK_STORE_ID": true + "newsletter_queue": { + "column": { + "queue_id": true, + "template_id": true, + "newsletter_type": true, + "newsletter_text": true, + "newsletter_styles": true, + "newsletter_subject": true, + "newsletter_sender_name": true, + "newsletter_sender_email": true, + "queue_status": true, + "queue_start_at": true, + "queue_finish_at": true + }, + "index": { + "NEWSLETTER_QUEUE_TEMPLATE_ID": true + }, + "constraint": { + "PRIMARY": true, + "NEWSLETTER_QUEUE_TEMPLATE_ID_NEWSLETTER_TEMPLATE_TEMPLATE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "NEWSLETTER_QUEUE_STORE_LINK_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, - "NEWSLETTER_QUEUE_STORE_LINK_STORE_ID_STORE_STORE_ID": true - } - }, - "newsletter_problem": { - "column": { - "problem_id": true, - "subscriber_id": true, - "queue_id": true, - "problem_error_code": true, - "problem_error_text": true + "newsletter_queue_link": { + "column": { + "queue_link_id": true, + "queue_id": true, + "subscriber_id": true, + "letter_sent_at": true + }, + "index": { + "NEWSLETTER_QUEUE_LINK_SUBSCRIBER_ID": true, + "NEWSLETTER_QUEUE_LINK_QUEUE_ID_LETTER_SENT_AT": true + }, + "constraint": { + "PRIMARY": true, + "NEWSLETTER_QUEUE_LINK_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, + "NLTTR_QUEUE_LNK_SUBSCRIBER_ID_NLTTR_SUBSCRIBER_SUBSCRIBER_ID": true + } }, - "index": { - "NEWSLETTER_PROBLEM_SUBSCRIBER_ID": true, - "NEWSLETTER_PROBLEM_QUEUE_ID": true + "newsletter_queue_store_link": { + "column": { + "queue_id": true, + "store_id": true + }, + "index": { + "NEWSLETTER_QUEUE_STORE_LINK_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "NEWSLETTER_QUEUE_STORE_LINK_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, + "NEWSLETTER_QUEUE_STORE_LINK_STORE_ID_STORE_STORE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "NEWSLETTER_PROBLEM_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, - "NLTTR_PROBLEM_SUBSCRIBER_ID_NLTTR_SUBSCRIBER_SUBSCRIBER_ID": true + "newsletter_problem": { + "column": { + "problem_id": true, + "subscriber_id": true, + "queue_id": true, + "problem_error_code": true, + "problem_error_text": true + }, + "index": { + "NEWSLETTER_PROBLEM_SUBSCRIBER_ID": true, + "NEWSLETTER_PROBLEM_QUEUE_ID": true + }, + "constraint": { + "PRIMARY": true, + "NEWSLETTER_PROBLEM_QUEUE_ID_NEWSLETTER_QUEUE_QUEUE_ID": true, + "NLTTR_PROBLEM_SUBSCRIBER_ID_NLTTR_SUBSCRIBER_SUBSCRIBER_ID": true + } } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml index b2e0edbf04452..c1a526ee95148 100644 --- a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml +++ b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml @@ -22,9 +22,9 @@ <label class="label" for="newsletter"><span><?= $block->escapeHtml(__('Sign Up for Our Newsletter:')) ?></span></label> <div class="control"> <input name="email" type="email" id="newsletter" - placeholder="<?= $block->escapeHtmlAttr(__('Enter your email address')) ?>" - data-mage-init='{"mage/trim-input":{}}' - data-validate="{required:true, 'validate-email':true}"/> + placeholder="<?= $block->escapeHtml(__('Enter your email address')) ?>" + data-mage-init='{"mage/trim-input":{}}' + data-validate="{required:true, 'validate-email':true}"/> </div> </div> <div class="actions"> diff --git a/app/code/Magento/OfflinePayments/Block/Form/Banktransfer.php b/app/code/Magento/OfflinePayments/Block/Form/Banktransfer.php index c4fe10c386645..d60348d9dc1c7 100644 --- a/app/code/Magento/OfflinePayments/Block/Form/Banktransfer.php +++ b/app/code/Magento/OfflinePayments/Block/Form/Banktransfer.php @@ -15,5 +15,5 @@ class Banktransfer extends \Magento\OfflinePayments\Block\Form\AbstractInstructi * * @var string */ - protected $_template = 'form/banktransfer.phtml'; + protected $_template = 'Magento_OfflinePayments::form/banktransfer.phtml'; } diff --git a/app/code/Magento/OfflinePayments/Block/Form/Cashondelivery.php b/app/code/Magento/OfflinePayments/Block/Form/Cashondelivery.php index 4e0f7d48ce09b..de0f7a57bae62 100644 --- a/app/code/Magento/OfflinePayments/Block/Form/Cashondelivery.php +++ b/app/code/Magento/OfflinePayments/Block/Form/Cashondelivery.php @@ -15,5 +15,5 @@ class Cashondelivery extends \Magento\OfflinePayments\Block\Form\AbstractInstruc * * @var string */ - protected $_template = 'form/cashondelivery.phtml'; + protected $_template = 'Magento_OfflinePayments::form/cashondelivery.phtml'; } diff --git a/app/code/Magento/OfflinePayments/Test/Mftf/composer.json b/app/code/Magento/OfflinePayments/Test/Mftf/composer.json deleted file mode 100644 index 7ce98340d75ee..0000000000000 --- a/app/code/Magento/OfflinePayments/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-offline-payments", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml b/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml index b47bd8f749040..89cc4d0986a00 100644 --- a/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflinePayments/etc/adminhtml/system.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="payment" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="payment" type="text" sortOrder="400" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="checkmo" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Check / Money Order</label> <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> diff --git a/app/code/Magento/OfflineShipping/Model/Quote/Address/FreeShipping.php b/app/code/Magento/OfflineShipping/Model/Quote/Address/FreeShipping.php index e1397dc6e097b..c6234a1247a53 100644 --- a/app/code/Magento/OfflineShipping/Model/Quote/Address/FreeShipping.php +++ b/app/code/Magento/OfflineShipping/Model/Quote/Address/FreeShipping.php @@ -3,27 +3,35 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\OfflineShipping\Model\Quote\Address; -class FreeShipping implements \Magento\Quote\Model\Quote\Address\FreeShippingInterface +use Magento\OfflineShipping\Model\SalesRule\Calculator; +use Magento\OfflineShipping\Model\SalesRule\ExtendedCalculator; +use Magento\Quote\Model\Quote\Address\FreeShippingInterface; +use Magento\Quote\Model\Quote\Item\AbstractItem; +use Magento\Store\Model\StoreManagerInterface; + +class FreeShipping implements FreeShippingInterface { /** - * @var \Magento\OfflineShipping\Model\SalesRule\Calculator + * @var ExtendedCalculator */ protected $calculator; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\OfflineShipping\Model\SalesRule\Calculator $calculator + * @param StoreManagerInterface $storeManager + * @param Calculator $calculator */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\OfflineShipping\Model\SalesRule\Calculator $calculator + StoreManagerInterface $storeManager, + Calculator $calculator ) { $this->storeManager = $storeManager; $this->calculator = $calculator; @@ -39,6 +47,7 @@ public function isFreeShipping(\Magento\Quote\Model\Quote $quote, $items) return false; } + $result = false; $addressFreeShipping = true; $store = $this->storeManager->getStore($quote->getStoreId()); $this->calculator->init( @@ -62,21 +71,24 @@ public function isFreeShipping(\Magento\Quote\Model\Quote $quote, $items) } $this->calculator->processFreeShipping($item); - $itemFreeShipping = (bool)$item->getFreeShipping(); - $addressFreeShipping = $addressFreeShipping && $itemFreeShipping; - - if ($addressFreeShipping && !$item->getAddress()->getFreeShipping()) { - $item->getAddress()->setFreeShipping(true); + // at least one item matches to the rule and the rule mode is not a strict + if ((bool)$item->getAddress()->getFreeShipping()) { + $result = true; + break; } - /** Parent free shipping we apply to all children*/ - $this->applyToChildren($item, $itemFreeShipping); + $itemFreeShipping = (bool)$item->getFreeShipping(); + $addressFreeShipping = $addressFreeShipping && $itemFreeShipping; + $result = $addressFreeShipping; } - return (bool)$shippingAddress->getFreeShipping(); + + $shippingAddress->setFreeShipping((int)$result); + $this->applyToItems($items, $result); + return $result; } /** - * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item + * @param AbstractItem $item * @param bool $isFreeShipping * @return void */ @@ -91,4 +103,20 @@ protected function applyToChildren(\Magento\Quote\Model\Quote\Item\AbstractItem } } } + + /** + * Sets free shipping availability to the quote items. + * + * @param array $items + * @param bool $freeShipping + */ + private function applyToItems(array $items, bool $freeShipping) + { + /** @var AbstractItem $item */ + foreach ($items as $item) { + $item->getAddress() + ->setFreeShipping((int)$freeShipping); + $this->applyToChildren($item, $freeShipping); + } + } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php index fc36eaf6a97db..0c4718245928b 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate.php @@ -320,7 +320,7 @@ public function getConditionName(\Magento\Framework\DataObject $object) */ private function getCsvFile($filePath) { - $pathInfo = pathInfo($filePath); + $pathInfo = pathinfo($filePath); $dirName = isset($pathInfo['dirname']) ? $pathInfo['dirname'] : ''; $fileName = isset($pathInfo['basename']) ? $pathInfo['basename'] : ''; diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/composer.json b/app/code/Magento/OfflineShipping/Test/Mftf/composer.json deleted file mode 100644 index acca01f787402..0000000000000 --- a/app/code/Magento/OfflineShipping/Test/Mftf/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "magento/functional-test-module-offline-shipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-offline-shipping-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/Quote/Address/FreeShippingTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/Quote/Address/FreeShippingTest.php index 5a8b1afa786bb..8266a39bbbb6d 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Model/Quote/Address/FreeShippingTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/Quote/Address/FreeShippingTest.php @@ -3,94 +3,193 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\OfflineShipping\Test\Unit\Model\Quote\Address; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\OfflineShipping\Model\Quote\Address\FreeShipping; +use Magento\OfflineShipping\Model\SalesRule\Calculator; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\Quote\Item; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; class FreeShippingTest extends \PHPUnit\Framework\TestCase { + private static $websiteId = 1; + + private static $customerGroupId = 2; + + private static $couponCode = 3; + + private static $storeId = 1; + /** - * @var \Magento\OfflineShipping\Model\Quote\Address\FreeShipping + * @var FreeShipping */ private $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\StoreManagerInterface + * @var MockObject|StoreManagerInterface */ - private $storeManagerMock; + private $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\OfflineShipping\Model\SalesRule\Calculator + * @var MockObject|Calculator */ - private $calculatorMock; + private $calculator; protected function setUp() { - $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->calculatorMock = $this->createMock(\Magento\OfflineShipping\Model\SalesRule\Calculator::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->calculator = $this->createMock(Calculator::class); - $this->model = new \Magento\OfflineShipping\Model\Quote\Address\FreeShipping( - $this->storeManagerMock, - $this->calculatorMock + $this->model = new FreeShipping( + $this->storeManager, + $this->calculator ); } - public function testIsFreeShippingIfNoItems() + /** + * Checks free shipping availability based on quote items and cart rule calculations. + * + * @param int $addressFree + * @param int $fItemFree + * @param int $sItemFree + * @param bool $expected + * @dataProvider itemsDataProvider + */ + public function testIsFreeShipping(int $addressFree, int $fItemFree, int $sItemFree, bool $expected) { - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->assertFalse($this->model->isFreeShipping($quoteMock, [])); + $address = $this->getShippingAddress(); + $this->withStore(); + $quote = $this->getQuote($address); + $fItem = $this->getItem($quote); + $sItem = $this->getItem($quote); + $items = [$fItem, $sItem]; + + $this->calculator->method('init') + ->with(self::$websiteId, self::$customerGroupId, self::$couponCode); + $this->calculator->method('processFreeShipping') + ->withConsecutive( + [$fItem], + [$sItem] + ) + ->willReturnCallback(function () use ($fItem, $sItem, $addressFree, $fItemFree, $sItemFree) { + // emulate behavior of cart rule calculator + $fItem->getAddress()->setFreeShipping($addressFree); + $fItem->setFreeShipping($fItemFree); + $sItem->setFreeShipping($sItemFree); + }); + + $actual = $this->model->isFreeShipping($quote, $items); + self::assertEquals($expected, $actual); + self::assertEquals($expected, $address->getFreeShipping()); } - public function testIsFreeShipping() + /** + * Gets list of variations with free shipping availability. + * + * @return array + */ + public function itemsDataProvider(): array { - $storeId = 100; - $websiteId = 200; - $customerGroupId = 300; - $objectManagerMock = new ObjectManagerHelper($this); - $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, - ['getShippingAddress', 'getStoreId', 'getCustomerGroupId', 'getCouponCode'] - ); - $itemMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, [ - 'getNoDiscount', - 'getParentItemId', - 'getFreeShipping', - 'getAddress', - 'isChildrenCalculated', - 'getHasChildren', - 'getChildren' - ]); - - $quoteMock->expects($this->once())->method('getStoreId')->willReturn($storeId); - $storeMock = $this->createMock(\Magento\Store\Api\Data\StoreInterface::class); - $storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId); - $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); - - $quoteMock->expects($this->once())->method('getCustomerGroupId')->willReturn($customerGroupId); - $quoteMock->expects($this->once())->method('getCouponCode')->willReturn(null); - - $this->calculatorMock->expects($this->once()) - ->method('init') - ->with($websiteId, $customerGroupId, null) - ->willReturnSelf(); - - $itemMock->expects($this->once())->method('getNoDiscount')->willReturn(false); - $itemMock->expects($this->once())->method('getParentItemId')->willReturn(false); - $this->calculatorMock->expects($this->exactly(2))->method('processFreeShipping')->willReturnSelf(); - $itemMock->expects($this->once())->method('getFreeShipping')->willReturn(true); - - $addressMock = $objectManagerMock->getObject(\Magento\Quote\Model\Quote\Address::class); - $quoteMock->expects($this->once())->method('getShippingAddress')->willReturn($addressMock); - $itemMock->expects($this->exactly(2))->method('getAddress')->willReturn($addressMock); - - $itemMock->expects($this->once())->method('getHasChildren')->willReturn(true); - $itemMock->expects($this->once())->method('isChildrenCalculated')->willReturn(true); - - $childMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, ['setFreeShipping']); - $childMock->expects($this->once())->method('setFreeShipping')->with(true)->willReturnSelf(); - $itemMock->expects($this->once())->method('getChildren')->willReturn([$childMock]); - - $this->assertTrue($this->model->isFreeShipping($quoteMock, [$itemMock])); + return [ + ['addressFree' => 1, 'fItemFree' => 0, 'sItemFree' => 0, 'expected' => true], + ['addressFree' => 0, 'fItemFree' => 1, 'sItemFree' => 0, 'expected' => false], + ['addressFree' => 0, 'fItemFree' => 0, 'sItemFree' => 1, 'expected' => false], + ['addressFree' => 0, 'fItemFree' => 1, 'sItemFree' => 1, 'expected' => true], + ]; + } + + /** + * Creates mock object for store entity. + */ + private function withStore() + { + $store = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager->method('getStore') + ->with(self::$storeId) + ->willReturn($store); + + $store->method('getWebsiteId') + ->willReturn(self::$websiteId); + } + + /** + * Get mock object for quote entity. + * + * @param Address $address + * @return Quote + */ + private function getQuote(Address $address): Quote + { + /** @var Quote|MockObject $quote */ + $quote = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'getCouponCode', 'getCustomerGroupId', 'getShippingAddress', 'getStoreId', 'getItemsQty', + 'getVirtualItemsQty' + ] + ) + ->getMock(); + + $quote->method('getStoreId') + ->willReturn(self::$storeId); + $quote->method('getCustomerGroupId') + ->willReturn(self::$customerGroupId); + $quote->method('getCouponCode') + ->willReturn(self::$couponCode); + $quote->method('getShippingAddress') + ->willReturn($address); + $quote->method('getItemsQty') + ->willReturn(2); + $quote->method('getVirtualItemsQty') + ->willReturn(0); + + return $quote; + } + + /** + * Gets stub object for shipping address. + * + * @return Address|MockObject + */ + private function getShippingAddress(): Address + { + /** @var Address|MockObject $address */ + $address = $this->getMockBuilder(Address::class) + ->disableOriginalConstructor() + ->setMethods(['beforeSave']) + ->getMock(); + + return $address; + } + + /** + * Gets stub object for quote item. + * + * @param Quote $quote + * @return Item + */ + private function getItem(Quote $quote): Item + { + /** @var Item|MockObject $item */ + $item = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->setMethods(['getHasChildren']) + ->getMock(); + $item->setQuote($quote); + $item->setNoDiscount(0); + $item->setParentItemId(0); + $item->method('getHasChildren') + ->willReturn(0); + + return $item; } } diff --git a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml index 306aac1769913..4db5f489aa4a2 100644 --- a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml @@ -7,7 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="carriers" translate="label" type="text" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="carriers" type="text" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="flatrate" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Flat Rate</label> <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> diff --git a/app/code/Magento/OfflineShipping/etc/db_schema_whitelist.json b/app/code/Magento/OfflineShipping/etc/db_schema_whitelist.json index 8a189e711c222..72da62b8159c6 100644 --- a/app/code/Magento/OfflineShipping/etc/db_schema_whitelist.json +++ b/app/code/Magento/OfflineShipping/etc/db_schema_whitelist.json @@ -1,44 +1,44 @@ { - "shipping_tablerate": { - "column": { - "pk": true, - "website_id": true, - "dest_country_id": true, - "dest_region_id": true, - "dest_zip": true, - "condition_name": true, - "condition_value": true, - "price": true, - "cost": true + "shipping_tablerate": { + "column": { + "pk": true, + "website_id": true, + "dest_country_id": true, + "dest_region_id": true, + "dest_zip": true, + "condition_name": true, + "condition_value": true, + "price": true, + "cost": true + }, + "constraint": { + "PRIMARY": true, + "UNQ_D60821CDB2AFACEE1566CFC02D0D4CAA": true + } }, - "constraint": { - "PRIMARY": true, - "UNQ_D60821CDB2AFACEE1566CFC02D0D4CAA": true - } - }, - "salesrule": { - "column": { - "simple_free_shipping": true - } - }, - "sales_order_item": { - "column": { - "free_shipping": true - } - }, - "quote_address": { - "column": { - "free_shipping": true - } - }, - "quote_item": { - "column": { - "free_shipping": true - } - }, - "quote_address_item": { - "column": { - "free_shipping": true + "salesrule": { + "column": { + "simple_free_shipping": true + } + }, + "sales_order_item": { + "column": { + "free_shipping": true + } + }, + "quote_address": { + "column": { + "free_shipping": true + } + }, + "quote_item": { + "column": { + "free_shipping": true + } + }, + "quote_address_item": { + "column": { + "free_shipping": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/PageCache/Model/Config.php b/app/code/Magento/PageCache/Model/Config.php index 6debbdf3e728a..83db8c0dec3b1 100644 --- a/app/code/Magento/PageCache/Model/Config.php +++ b/app/code/Magento/PageCache/Model/Config.php @@ -113,7 +113,6 @@ public function __construct( * * @return int * @api - * @deprecated 100.2.0 see \Magento\PageCache\Model\VclGeneratorInterface::generateVcl */ public function getType() { @@ -125,7 +124,6 @@ public function getType() * * @return int * @api - * @deprecated 100.2.0 see \Magento\PageCache\Model\VclGeneratorInterface::generateVcl */ public function getTtl() { @@ -255,7 +253,6 @@ protected function _getDesignExceptions() * * @return bool * @api - * @deprecated 100.2.0 see \Magento\PageCache\Model\VclGeneratorInterface::generateVcl */ public function isEnabled() { diff --git a/app/code/Magento/PageCache/Observer/RegisterFormKeyFromCookie.php b/app/code/Magento/PageCache/Observer/RegisterFormKeyFromCookie.php deleted file mode 100644 index 4b51a9cf233a1..0000000000000 --- a/app/code/Magento/PageCache/Observer/RegisterFormKeyFromCookie.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\PageCache\Observer; - -use Magento\Framework\Event\ObserverInterface; - -class RegisterFormKeyFromCookie implements ObserverInterface -{ - /** - * @var \Magento\Framework\App\PageCache\FormKey - */ - private $cookieFormKey; - - /** - * @var \Magento\Framework\Escaper - */ - private $escaper; - - /** - * @var \Magento\Framework\Data\Form\FormKey - */ - private $sessionFormKey; - - /** - * @var \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory - */ - private $cookieMetadataFactory; - - /** - * @var \Magento\Framework\Session\Config\ConfigInterface - */ - private $sessionConfig; - - /** - * @param \Magento\Framework\App\PageCache\FormKey $formKey - * @param \Magento\Framework\Escaper $escaper - * @param \Magento\Framework\Data\Form\FormKey $sessionFormKey - * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory - * @param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig - */ - public function __construct( - \Magento\Framework\App\PageCache\FormKey $formKey, - \Magento\Framework\Escaper $escaper, - \Magento\Framework\Data\Form\FormKey $sessionFormKey, - \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory, - \Magento\Framework\Session\Config\ConfigInterface $sessionConfig - ) { - $this->cookieFormKey = $formKey; - $this->escaper = $escaper; - $this->sessionFormKey = $sessionFormKey; - $this->cookieMetadataFactory = $cookieMetadataFactory; - $this->sessionConfig = $sessionConfig; - } - - /** - * Register form key in session from cookie value - * - * @param \Magento\Framework\Event\Observer $observer - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - if ($this->cookieFormKey->get()) { - $this->updateCookieFormKey($this->cookieFormKey->get()); - - $this->sessionFormKey->set( - $this->escaper->escapeHtml($this->cookieFormKey->get()) - ); - } - } - - /** - * @param string $formKey - * @return void - */ - private function updateCookieFormKey($formKey) - { - $cookieMetadata = $this->cookieMetadataFactory - ->createPublicCookieMetadata(); - $cookieMetadata->setDomain($this->sessionConfig->getCookieDomain()); - $cookieMetadata->setPath($this->sessionConfig->getCookiePath()); - $lifetime = $this->sessionConfig->getCookieLifetime(); - if ($lifetime !== 0) { - $cookieMetadata->setDuration($lifetime); - } - - $this->cookieFormKey->set( - $formKey, - $cookieMetadata - ); - } -} diff --git a/app/code/Magento/PageCache/Plugin/RegisterFormKeyFromCookie.php b/app/code/Magento/PageCache/Plugin/RegisterFormKeyFromCookie.php new file mode 100644 index 0000000000000..6cdc500aaf33c --- /dev/null +++ b/app/code/Magento/PageCache/Plugin/RegisterFormKeyFromCookie.php @@ -0,0 +1,107 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Plugin; + +use Magento\Framework\App\PageCache\FormKey as CacheFormKey; +use Magento\Framework\Escaper; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; +use Magento\Framework\Session\Config\ConfigInterface; + +/** + * Allow for registration of a form key through cookies. + */ +class RegisterFormKeyFromCookie +{ + /** + * @var CacheFormKey + */ + private $cookieFormKey; + + /** + * @var Escaper + */ + private $escaper; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @var CookieMetadataFactory + */ + private $cookieMetadataFactory; + + /** + * @var ConfigInterface + */ + private $sessionConfig; + + /** + * @param CacheFormKey $formKey + * @param Escaper $escaper + * @param FormKey $formKey + * @param CookieMetadataFactory $cookieMetadataFactory + * @param ConfigInterface $sessionConfig + */ + public function __construct( + CacheFormKey $cacheFormKey, + Escaper $escaper, + FormKey $formKey, + CookieMetadataFactory $cookieMetadataFactory, + ConfigInterface $sessionConfig + ) { + $this->cookieFormKey = $cacheFormKey; + $this->escaper = $escaper; + $this->formKey = $formKey; + $this->cookieMetadataFactory = $cookieMetadataFactory; + $this->sessionConfig = $sessionConfig; + } + + /** + * Set form key from the cookie. + * + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeDispatch(): void + { + if ($this->cookieFormKey->get()) { + $this->updateCookieFormKey($this->cookieFormKey->get()); + + $this->formKey->set( + $this->escaper->escapeHtml($this->cookieFormKey->get()) + ); + } + } + + /** + * @param string $formKey + * @return void + */ + private function updateCookieFormKey(string $formKey): void + { + $cookieMetadata = $this->cookieMetadataFactory + ->createPublicCookieMetadata(); + $cookieMetadata->setDomain($this->sessionConfig->getCookieDomain()); + $cookieMetadata->setPath($this->sessionConfig->getCookiePath()); + $lifetime = $this->sessionConfig->getCookieLifetime(); + if ($lifetime !== 0) { + $cookieMetadata->setDuration($lifetime); + } + + $this->cookieFormKey->set( + $formKey, + $cookieMetadata + ); + } +} diff --git a/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearCacheActionGroup.xml b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearCacheActionGroup.xml index cf4bd4ff43c8e..510dfc3554ce5 100644 --- a/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearCacheActionGroup.xml +++ b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearCacheActionGroup.xml @@ -7,11 +7,19 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> -<actionGroup name="ClearCacheActionGroup"> - <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/cache/" stepKey="goToNewCustomVarialePage" /> - <waitForPageLoad stepKey="waitForPageLoad"/> - <click selector="{{AdminCacheManagementSection.FlushMagentoCache}}" stepKey="clickFlushMagentoCache" /> - <waitForPageLoad stepKey="waitForCacheFlush"/> -</actionGroup> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ClearCacheActionGroup"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/cache/" stepKey="goToNewCustomVarialePage" /> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminCacheManagementSection.FlushMagentoCache}}" stepKey="clickFlushMagentoCache" /> + <waitForPageLoad stepKey="waitForCacheFlush"/> + </actionGroup> + <actionGroup name="clearPageCache"> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/cache/" stepKey="amOnCacheManagementPage"/> + <waitForPageLoad stepKey="waitForCacheManagement"/> + <selectOption selector="{{AdminCacheManagementSection.massActionSelect}}" userInput="refresh" stepKey="selectRefresh"/> + <click selector="{{AdminCacheManagementSection.pageCacheCheckbox}}" stepKey="selectPageCache"/> + <click selector="{{AdminCacheManagementSection.massActionSubmit}}" stepKey="submitCacheForm"/> + <waitForPageLoad stepKey="waitForCacheFlush"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearPageCacheActionGroup.xml b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearPageCacheActionGroup.xml index 0293b1e10b8ce..35d5234e47c3a 100644 --- a/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearPageCacheActionGroup.xml +++ b/app/code/Magento/PageCache/Test/Mftf/ActionGroup/ClearPageCacheActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="ClearPageCacheActionGroup"> <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}/admin/cache/" stepKey="goToCacheManagementPage" /> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/PageCache/Test/Mftf/Page/AdminCacheManagementPage.xml b/app/code/Magento/PageCache/Test/Mftf/Page/AdminCacheManagementPage.xml new file mode 100644 index 0000000000000..24fc5ffb04cb7 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Mftf/Page/AdminCacheManagementPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCacheManagementPage" url="/admin/cache" area="admin" module="PageCache"> + <section name="AdminCacheManagementSection"/> + </page> +</pages> diff --git a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml index c91f021b5719b..34a77095d524d 100644 --- a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml +++ b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml @@ -7,12 +7,28 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCacheManagementSection"> <element name="FlushMagentoCache" type="button" selector="#flush_magento"/> <element name="actionDropDown" type="multiselect" selector="//*[@id='cache_grid_massaction-select']//option[contains(., 'Action')]" timeout="30"/> <element name="refreshOption" type="multiselect" selector="//*[@id='cache_grid_massaction-select']//option[@value='refresh']" timeout="30"/> <element name="pageCacheRowCheckbox" type="checkbox" selector="//td[contains(., 'Page Cache')]/..//input[@type='checkbox']"/> <element name="submit" type="button" selector="//button[@title='Submit']" timeout="30"/> + <element name="massActionSelect" type="select" selector="#cache_grid_massaction-form #cache_grid_massaction-select"/> + <element name="massActionSubmit" type="button" selector="#cache_grid_massaction-form button"/> + <element name="configurationCheckbox" type="checkbox" selector="input[value='config']"/> + <element name="layoutsCheckbox" type="checkbox" selector="input[value='layout']"/> + <element name="blocksHtmlCheckbox" type="checkbox" selector="input[value='block_html']"/> + <element name="collectionsDataCheckbox" type="checkbox" selector="input[value='collections']"/> + <element name="reflectionDataCheckbox" type="checkbox" selector="input[value='reflection']"/> + <element name="databaseDdlCheckbox" type="checkbox" selector="input[value='db_ddl']"/> + <element name="compiledConfigCheckbox" type="checkbox" selector="input[value='compiled_config']"/> + <element name="eavTypesAndAttrsCheckbox" type="checkbox" selector="input[value='eav']"/> + <element name="customerNotificationCheckbox" type="checkbox" selector="input[value='customer_notification']"/> + <element name="integrationsConfigCheckbox" type="checkbox" selector="input[value='config_integration']"/> + <element name="integrationsApiCheckbox" type="checkbox" selector="input[value='config_integration_api']"/> + <element name="pageCacheCheckbox" type="checkbox" selector="input[value='full_page']"/> + <element name="webServicesConfigCheckbox" type="checkbox" selector="input[value='config_webservice']"/> + <element name="translationsCheckbox" type="checkbox" selector="input[value='translate']"/> </section> </sections> diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml new file mode 100644 index 0000000000000..c5871ddc3a373 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="NewProductsListWidgetSimpleProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> + <test name="NewProductsListWidgetConfigurableProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> + <test name="NewProductsListWidgetGroupedProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> + <test name="NewProductsListWidgetVirtualProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> + <test name="NewProductsListWidgetBundleProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> + <test name="NewProductsListWidgetDownloadableProductTest"> + <actionGroup ref="clearPageCache" stepKey="clearPageCache" after="clickSaveProduct"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/PageCache/Test/Mftf/composer.json b/app/code/Magento/PageCache/Test/Mftf/composer.json deleted file mode 100644 index 3bfe8f15ef64e..0000000000000 --- a/app/code/Magento/PageCache/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-page-cache", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/PageCache/Test/Unit/Observer/RegisterFormKeyFromCookieTest.php b/app/code/Magento/PageCache/Test/Unit/Observer/RegisterFormKeyFromCookieTest.php deleted file mode 100644 index 7bdea2e427c0b..0000000000000 --- a/app/code/Magento/PageCache/Test/Unit/Observer/RegisterFormKeyFromCookieTest.php +++ /dev/null @@ -1,169 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\PageCache\Test\Unit\Observer; - -use Magento\Framework\App\PageCache\FormKey; -use Magento\Framework\Escaper; -use Magento\Framework\Session\Config\ConfigInterface; -use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; -use Magento\PageCache\Observer\RegisterFormKeyFromCookie; - -class RegisterFormKeyFromCookieTest extends \PHPUnit\Framework\TestCase -{ - /** @var RegisterFormKeyFromCookie */ - protected $observer; - - /** @var \PHPUnit_Framework_MockObject_MockObject|FormKey */ - protected $cookieFormKey; - - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Data\Form\FormKey */ - protected $sessionFormKey; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|CookieMetadataFactory - */ - protected $cookieMetadataFactory; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|ConfigInterface - */ - protected $sessionConfig; - - /** @var \PHPUnit_Framework_MockObject_MockObject|Escaper */ - protected $escaper; - - /** - * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject| - */ - protected $observerMock; - - /** - * Set up all mocks and data for test - */ - protected function setUp() - { - $this->cookieFormKey = $this->getMockBuilder( - \Magento\Framework\App\PageCache\FormKey::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->escaper = $this->getMockBuilder( - \Magento\Framework\Escaper::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->sessionFormKey = $this->getMockBuilder( - \Magento\Framework\Data\Form\FormKey::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->cookieMetadataFactory = $this->getMockBuilder( - \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->sessionConfig = $this->createMock( - \Magento\Framework\Session\Config\ConfigInterface::class - ); - - $this->observerMock = $this->createMock(\Magento\Framework\Event\Observer::class); - - $this->observer = new RegisterFormKeyFromCookie( - $this->cookieFormKey, - $this->escaper, - $this->sessionFormKey, - $this->cookieMetadataFactory, - $this->sessionConfig - ); - } - - public function testExecuteNoCookie() - { - $this->cookieFormKey->expects(static::once()) - ->method('get') - ->willReturn(null); - $this->cookieFormKey->expects(static::never()) - ->method('set'); - $this->sessionFormKey->expects(static::never()) - ->method('set'); - - $this->observer->execute($this->observerMock); - } - - public function testExecute() - { - $formKey = 'form_key'; - $escapedFormKey = 'escaped_form_key'; - $cookieDomain = 'example.com'; - $cookiePath = '/'; - $cookieLifetime = 3600; - - $cookieMetadata = $this->getMockBuilder( - \Magento\Framework\Stdlib\Cookie\PublicCookieMetadata::class - ) - ->disableOriginalConstructor() - ->getMock(); - - $this->cookieFormKey->expects(static::any()) - ->method('get') - ->willReturn($formKey); - $this->cookieMetadataFactory->expects(static::once()) - ->method('createPublicCookieMetadata') - ->willReturn( - $cookieMetadata - ); - - $this->sessionConfig->expects(static::once()) - ->method('getCookieDomain') - ->willReturn( - $cookieDomain - ); - $cookieMetadata->expects(static::once()) - ->method('setDomain') - ->with( - $cookieDomain - ); - $this->sessionConfig->expects(static::once()) - ->method('getCookiePath') - ->willReturn( - $cookiePath - ); - $cookieMetadata->expects(static::once()) - ->method('setPath') - ->with( - $cookiePath - ); - $this->sessionConfig->expects(static::once()) - ->method('getCookieLifetime') - ->willReturn( - $cookieLifetime - ); - $cookieMetadata->expects(static::once()) - ->method('setDuration') - ->with( - $cookieLifetime - ); - - $this->cookieFormKey->expects(static::once()) - ->method('set') - ->with( - $formKey, - $cookieMetadata - ); - - $this->escaper->expects(static::once()) - ->method('escapeHtml') - ->with($formKey) - ->willReturn($escapedFormKey); - - $this->sessionFormKey->expects(static::once()) - ->method('set') - ->with($escapedFormKey); - - $this->observer->execute($this->observerMock); - } -} diff --git a/app/code/Magento/PageCache/etc/adminhtml/system.xml b/app/code/Magento/PageCache/etc/adminhtml/system.xml index 1d6c0d890737b..5055b17956819 100644 --- a/app/code/Magento/PageCache/etc/adminhtml/system.xml +++ b/app/code/Magento/PageCache/etc/adminhtml/system.xml @@ -49,7 +49,7 @@ <field id="caching_application">1</field> </depends> </field> - <field id="export_button_version4" type="button" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="export_button_version4" translate="label" type="button" sortOrder="35" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Export Configuration</label> <frontend_model>Magento\PageCache\Block\System\Config\Form\Field\Export\Varnish4</frontend_model> <depends> diff --git a/app/code/Magento/PageCache/etc/di.xml b/app/code/Magento/PageCache/etc/di.xml index 2a9abbb860805..adf9526fc1108 100644 --- a/app/code/Magento/PageCache/etc/di.xml +++ b/app/code/Magento/PageCache/etc/di.xml @@ -37,6 +37,9 @@ <argument name="layoutCacheKey" xsi:type="object">Magento\Framework\View\Layout\LayoutCacheKeyInterface</argument> </arguments> </type> + <type name="Magento\Framework\App\FrontControllerInterface"> + <plugin name="page_cache_from_key_from_cookie" type="Magento\PageCache\Plugin\RegisterFormKeyFromCookie" /> + </type> <preference for="Magento\PageCache\Model\VclGeneratorInterface" type="Magento\PageCache\Model\Varnish\VclGenerator"/> <preference for="Magento\PageCache\Model\VclTemplateLocatorInterface" type="Magento\PageCache\Model\Varnish\VclTemplateLocator"/> </config> diff --git a/app/code/Magento/PageCache/etc/frontend/events.xml b/app/code/Magento/PageCache/etc/frontend/events.xml deleted file mode 100644 index ce2c1c823e835..0000000000000 --- a/app/code/Magento/PageCache/etc/frontend/events.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> - <event name="controller_action_predispatch"> - <observer name="register_form_key" instance="Magento\PageCache\Observer\RegisterFormKeyFromCookie" /> - </event> -</config> diff --git a/app/code/Magento/Payment/Block/Info/Instructions.php b/app/code/Magento/Payment/Block/Info/Instructions.php index e3c74e020f8f6..687c6b54a2f4f 100644 --- a/app/code/Magento/Payment/Block/Info/Instructions.php +++ b/app/code/Magento/Payment/Block/Info/Instructions.php @@ -23,7 +23,7 @@ class Instructions extends \Magento\Payment\Block\Info /** * @var string */ - protected $_template = 'info/instructions.phtml'; + protected $_template = 'Magento_Payment::info/instructions.phtml'; /** * Get instructions text from order payment diff --git a/app/code/Magento/Payment/Helper/Data.php b/app/code/Magento/Payment/Helper/Data.php index 5fd23c195f0c4..0a4990313fa82 100644 --- a/app/code/Magento/Payment/Helper/Data.php +++ b/app/code/Magento/Payment/Helper/Data.php @@ -151,15 +151,7 @@ public function getStoreMethods($store = null, $quote = null) @uasort( $res, function (MethodInterface $a, MethodInterface $b) { - if ((int)$a->getConfigData('sort_order') < (int)$b->getConfigData('sort_order')) { - return -1; - } - - if ((int)$a->getConfigData('sort_order') > (int)$b->getConfigData('sort_order')) { - return 1; - } - - return 0; + return (int)$a->getConfigData('sort_order') <=> (int)$b->getConfigData('sort_order'); } ); diff --git a/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodData.xml b/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodData.xml index feaff74a3a985..14c8bd0fecde7 100644 --- a/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodData.xml +++ b/app/code/Magento/Payment/Test/Mftf/Data/PaymentMethodData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="PaymentMethodCheckMoneyOrder" type="payment_method"> <data key="method">checkmo</data> </entity> diff --git a/app/code/Magento/Payment/Test/Mftf/Metadata/payment_method-meta.xml b/app/code/Magento/Payment/Test/Mftf/Metadata/payment_method-meta.xml index eb5f272e25e25..39506a682038f 100644 --- a/app/code/Magento/Payment/Test/Mftf/Metadata/payment_method-meta.xml +++ b/app/code/Magento/Payment/Test/Mftf/Metadata/payment_method-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreatePaymentMethod" dataType="payment_method" type="create"> <field key="method">string</field> </operation> diff --git a/app/code/Magento/Payment/Test/Mftf/composer.json b/app/code/Magento/Payment/Test/Mftf/composer.json deleted file mode 100644 index 7311dae6fb074..0000000000000 --- a/app/code/Magento/Payment/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-payment", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml index f1988f1ca86bb..c127371cb5f47 100644 --- a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml +++ b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml @@ -67,9 +67,10 @@ $params = $block->getParams(); 'jquery', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/action/place-order', - 'Magento_Checkout/js/action/redirect-on-success' + 'Magento_Checkout/js/action/redirect-on-success', + 'Magento_Checkout/js/model/full-screen-loader' ], - function($, quote, placeOrderAction, redirectOnSuccessAction) { + function($, quote, placeOrderAction, redirectOnSuccessAction, fullScreenLoader) { var parent = window.top; $(parent).trigger('clearTimeout'); @@ -79,6 +80,12 @@ $params = $block->getParams(); function () { redirectOnSuccessAction.execute(); } + ).fail( + function () { + var parent = window.top; + $(parent).trigger('clearTimeout'); + fullScreenLoader.stopLoader(); + } ); } ); diff --git a/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Form.php b/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Form.php index 31b3e0c1d6b6f..396c66d4e748e 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Form.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Form.php @@ -13,5 +13,5 @@ class Form extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'billing/agreement/view/form.phtml'; + protected $_template = 'Magento_Paypal::billing/agreement/view/form.phtml'; } diff --git a/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Tab/Info.php b/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Tab/Info.php index d133d19f9c202..39373017fa09a 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Tab/Info.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/Billing/Agreement/View/Tab/Info.php @@ -15,7 +15,7 @@ class Info extends \Magento\Backend\Block\Template implements \Magento\Backend\B /** * @var string */ - protected $_template = 'billing/agreement/view/tab/info.phtml'; + protected $_template = 'Magento_Paypal::billing/agreement/view/tab/info.phtml'; /** * Core registry diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/ApiWizard.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/ApiWizard.php index a1cfabd48440f..afa80892487a5 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/ApiWizard.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/ApiWizard.php @@ -13,7 +13,7 @@ class ApiWizard extends \Magento\Config\Block\System\Config\Form\Field /** * Path to block template */ - const WIZARD_TEMPLATE = 'system/config/api_wizard.phtml'; + const WIZARD_TEMPLATE = 'Magento_Paypal::system/config/api_wizard.phtml'; /** * Set template to itself diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/BmlApiWizard.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/BmlApiWizard.php index bdca98e089e7b..c481871e1fb0f 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/BmlApiWizard.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/BmlApiWizard.php @@ -11,7 +11,7 @@ class BmlApiWizard extends ApiWizard /** * Path to block template */ - const WIZARD_TEMPLATE = 'system/config/bml_api_wizard.phtml'; + const WIZARD_TEMPLATE = 'Magento_Paypal::system/config/bml_api_wizard.phtml'; /** * Get the button and scripts contents diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Advanced.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Advanced.php index fe4f6ee1f6757..793fad152bc7e 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Advanced.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Advanced.php @@ -16,5 +16,5 @@ class Advanced extends \Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink * * @var string */ - protected $_template = 'system/config/payflowlink/advanced.phtml'; + protected $_template = 'Magento_Paypal::system/config/payflowlink/advanced.phtml'; } diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php index 30119063f5b5b..405de1ec03185 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Payflowlink/Info.php @@ -16,7 +16,7 @@ class Info extends \Magento\Config\Block\System\Config\Form\Field * * @var string */ - protected $_template = 'system/config/payflowlink/info.phtml'; + protected $_template = 'Magento_Paypal::system/config/payflowlink/info.phtml'; /** * Render fieldset html diff --git a/app/code/Magento/Paypal/Block/Hosted/Pro/Form.php b/app/code/Magento/Paypal/Block/Hosted/Pro/Form.php index 92281d3058eef..70eff8f83ba99 100644 --- a/app/code/Magento/Paypal/Block/Hosted/Pro/Form.php +++ b/app/code/Magento/Paypal/Block/Hosted/Pro/Form.php @@ -15,5 +15,5 @@ class Form extends \Magento\Payment\Block\Form /** * @var string */ - protected $_template = 'hss/info.phtml'; + protected $_template = 'Magento_Paypal::hss/info.phtml'; } diff --git a/app/code/Magento/Paypal/Block/Iframe.php b/app/code/Magento/Paypal/Block/Iframe.php index d6edb1ed25231..bcdfae6b86dbe 100644 --- a/app/code/Magento/Paypal/Block/Iframe.php +++ b/app/code/Magento/Paypal/Block/Iframe.php @@ -44,7 +44,7 @@ class Iframe extends \Magento\Payment\Block\Form /** * @var string */ - protected $_template = 'hss/js.phtml'; + protected $_template = 'Magento_Paypal::hss/js.phtml'; /** * @var \Magento\Sales\Model\OrderFactory @@ -110,13 +110,13 @@ protected function _construct() if (in_array($paymentCode, $this->_hssHelper->getHssMethods())) { $this->_paymentMethodCode = $paymentCode; $templatePath = str_replace('_', '', $paymentCode); - $templateFile = "{$templatePath}/iframe.phtml"; + $templateFile = "Magento_Paypal::{$templatePath}/iframe.phtml"; $directory = $this->readFactory->create($this->reader->getModuleDir('', 'Magento_Paypal')); $file = $this->resolver->getTemplateFileName($templateFile, ['module' => 'Magento_Paypal']); if ($file && $directory->isExist($directory->getRelativePath($file))) { $this->setTemplate($templateFile); } else { - $this->setTemplate('hss/iframe.phtml'); + $this->setTemplate('Magento_Paypal::hss/iframe.phtml'); } } } @@ -198,7 +198,7 @@ protected function _beforeToHtml() protected function _toHtml() { if ($this->_isAfterPaymentSave()) { - $this->setTemplate('hss/js.phtml'); + $this->setTemplate('Magento_Paypal::hss/js.phtml'); return parent::_toHtml(); } if (!$this->_shouldRender) { diff --git a/app/code/Magento/Paypal/Block/Payflow/Advanced/Form.php b/app/code/Magento/Paypal/Block/Payflow/Advanced/Form.php index d777279ad47a8..159abd4e2f1bb 100644 --- a/app/code/Magento/Paypal/Block/Payflow/Advanced/Form.php +++ b/app/code/Magento/Paypal/Block/Payflow/Advanced/Form.php @@ -15,7 +15,7 @@ class Form extends \Magento\Paypal\Block\Payflow\Link\Form /** * @var string */ - protected $_template = 'payflowadvanced/info.phtml'; + protected $_template = 'Magento_Paypal::payflowadvanced/info.phtml'; /** * Get frame action URL diff --git a/app/code/Magento/Paypal/Block/Payflow/Link/Form.php b/app/code/Magento/Paypal/Block/Payflow/Link/Form.php index f414a63d69046..fc880f494c859 100644 --- a/app/code/Magento/Paypal/Block/Payflow/Link/Form.php +++ b/app/code/Magento/Paypal/Block/Payflow/Link/Form.php @@ -15,7 +15,7 @@ class Form extends \Magento\Payment\Block\Form /** * @var string */ - protected $_template = 'payflowlink/info.phtml'; + protected $_template = 'Magento_Paypal::payflowlink/info.phtml'; /** * Get frame action URL diff --git a/app/code/Magento/Paypal/Controller/Ipn/Index.php b/app/code/Magento/Paypal/Controller/Ipn/Index.php index ca2670292ec8a..8a7b755581509 100644 --- a/app/code/Magento/Paypal/Controller/Ipn/Index.php +++ b/app/code/Magento/Paypal/Controller/Ipn/Index.php @@ -7,12 +7,15 @@ namespace Magento\Paypal\Controller\Ipn; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\RemoteServiceUnavailableException; /** * Unified IPN controller for all supported PayPal methods */ -class Index extends \Magento\Framework\App\Action\Action +class Index extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface { /** * @var \Psr\Log\LoggerInterface @@ -39,6 +42,23 @@ public function __construct( parent::__construct($context); } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Instantiate IPN model and pass IPN request to it * diff --git a/app/code/Magento/Paypal/Controller/Payflow/CancelPayment.php b/app/code/Magento/Paypal/Controller/Payflow/CancelPayment.php index 39cc02942cf76..71ae6d4975fbe 100644 --- a/app/code/Magento/Paypal/Controller/Payflow/CancelPayment.php +++ b/app/code/Magento/Paypal/Controller/Payflow/CancelPayment.php @@ -6,8 +6,29 @@ */ namespace Magento\Paypal\Controller\Payflow; -class CancelPayment extends \Magento\Paypal\Controller\Payflow +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +class CancelPayment extends \Magento\Paypal\Controller\Payflow implements CsrfAwareActionInterface { + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * When a customer cancel payment from payflow gateway. * diff --git a/app/code/Magento/Paypal/Controller/Payflow/ReturnUrl.php b/app/code/Magento/Paypal/Controller/Payflow/ReturnUrl.php index a370eeb40eafd..56a5da40edb85 100644 --- a/app/code/Magento/Paypal/Controller/Payflow/ReturnUrl.php +++ b/app/code/Magento/Paypal/Controller/Payflow/ReturnUrl.php @@ -6,11 +6,14 @@ */ namespace Magento\Paypal\Controller\Payflow; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Paypal\Controller\Payflow; use Magento\Paypal\Model\Config; use Magento\Sales\Model\Order; -class ReturnUrl extends Payflow +class ReturnUrl extends Payflow implements CsrfAwareActionInterface { /** * @var array of allowed order states on frontend @@ -30,6 +33,23 @@ class ReturnUrl extends Payflow Config::METHOD_PAYFLOWLINK ]; + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * When a customer return to website from payflow gateway. * @@ -48,9 +68,10 @@ public function execute() if ($order->getIncrementId()) { if ($this->checkOrderState($order)) { $redirectBlock->setData('goto_success_page', true); + $this->_eventManager->dispatch('paypal_checkout_success', ['order' => $order]); } else { if ($this->checkPaymentMethod($order)) { - $gotoSection = $this->_cancelPayment(strval($this->getRequest()->getParam('RESPMSG'))); + $gotoSection = $this->_cancelPayment((string)$this->getRequest()->getParam('RESPMSG')); $redirectBlock->setData('goto_section', $gotoSection); $redirectBlock->setData('error_msg', __('Your payment has been declined. Please try again.')); } else { diff --git a/app/code/Magento/Paypal/Controller/Payflow/SilentPost.php b/app/code/Magento/Paypal/Controller/Payflow/SilentPost.php index e686c91fd4c9f..2d6a8f80bafd8 100644 --- a/app/code/Magento/Paypal/Controller/Payflow/SilentPost.php +++ b/app/code/Magento/Paypal/Controller/Payflow/SilentPost.php @@ -6,8 +6,29 @@ */ namespace Magento\Paypal\Controller\Payflow; -class SilentPost extends \Magento\Paypal\Controller\Payflow +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; + +class SilentPost extends \Magento\Paypal\Controller\Payflow implements CsrfAwareActionInterface { + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * Get response from PayPal by silent post method * diff --git a/app/code/Magento/Paypal/Controller/Transparent/Response.php b/app/code/Magento/Paypal/Controller/Transparent/Response.php index c54dd529588b9..4a4177822737d 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/Response.php +++ b/app/code/Magento/Paypal/Controller/Transparent/Response.php @@ -5,6 +5,9 @@ */ namespace Magento\Paypal\Controller\Transparent; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Registry; use Magento\Framework\App\Action\Context; use Magento\Framework\View\Result\LayoutFactory; @@ -18,9 +21,9 @@ use Magento\Framework\Session\Generic as Session; /** - * Class Response + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Response extends \Magento\Framework\App\Action\Action +class Response extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface { /** * Core registry @@ -91,6 +94,23 @@ public function __construct( $this->paymentFailures = $paymentFailures ?: $this->_objectManager->get(PaymentFailuresInterface::class); } + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + /** * @return ResultInterface */ diff --git a/app/code/Magento/Paypal/Model/Api/AbstractApi.php b/app/code/Magento/Paypal/Model/Api/AbstractApi.php index 0d1cd44639e93..6f578e44eae4f 100644 --- a/app/code/Magento/Paypal/Model/Api/AbstractApi.php +++ b/app/code/Magento/Paypal/Model/Api/AbstractApi.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Paypal\Model\Api; use Magento\Payment\Helper\Formatter; @@ -427,6 +429,7 @@ protected function _exportLineItems(array &$request, $i = 0) if (isset($this->_lineItemTotalExportMap[$key])) { // !empty($total) $privateKey = $this->_lineItemTotalExportMap[$key]; + $total = round($total, 2); $request[$privateKey] = $this->formatPrice($total); } } diff --git a/app/code/Magento/Paypal/Model/Cart.php b/app/code/Magento/Paypal/Model/Cart.php index c8e4a6acf4649..cef17fab8d916 100644 --- a/app/code/Magento/Paypal/Model/Cart.php +++ b/app/code/Magento/Paypal/Model/Cart.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Paypal\Model; /** @@ -177,7 +179,7 @@ protected function _applyDiscountTaxCompensationWorkaround( ) { $dataContainer = $salesEntity->getTaxContainer(); $this->addTax((double)$dataContainer->getBaseDiscountTaxCompensationAmount()); - $this->addTax((double)$dataContainer->getBaseShippingDiscountTaxCompensationAmnt()); + $this->addTax((double)$dataContainer->getBaseShippingDiscountTaxCompensationAmount()); } /** diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index 9c9b4dc3e87a7..16bcac300de97 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Paypal\Model\Express; use Magento\Customer\Api\Data\CustomerInterface as CustomerDataObject; @@ -498,7 +500,8 @@ public function start($returnUrl, $cancelUrl, $button = null) $solutionType = $this->_config->getMerchantCountry() == 'DE' ? \Magento\Paypal\Model\Config::EC_SOLUTION_TYPE_MARK : $this->_config->getValue('solutionType'); - $this->_getApi()->setAmount($this->_quote->getBaseGrandTotal()) + $totalAmount = round($this->_quote->getBaseGrandTotal(), 2); + $this->_getApi()->setAmount($totalAmount) ->setCurrencyCode($this->_quote->getBaseCurrencyCode()) ->setInvNum($this->_quote->getReservedOrderId()) ->setReturnUrl($returnUrl) @@ -809,8 +812,12 @@ public function place($token, $shippingMethodCode = null) case \Magento\Sales\Model\Order::STATE_PROCESSING: case \Magento\Sales\Model\Order::STATE_COMPLETE: case \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW: - if (!$order->getEmailSent()) { - $this->orderSender->send($order); + try { + if (!$order->getEmailSent()) { + $this->orderSender->send($order); + } + } catch (\Exception $e) { + $this->_logger->critical($e); } $this->_checkoutSession->start(); break; diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml index 4cb6173a0273e..6d5f80e30dc7f 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SamplePaypalConfig" type="paypal_config_state"> <requiredEntity type="business_account">SampleBusinessAccount</requiredEntity> <requiredEntity type="api_username">SampleApiUsername</requiredEntity> diff --git a/app/code/Magento/Paypal/Test/Mftf/Metadata/paypal_config-meta.xml b/app/code/Magento/Paypal/Test/Mftf/Metadata/paypal_config-meta.xml index 37009297ddb30..7457a90150a0f 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Metadata/paypal_config-meta.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Metadata/paypal_config-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreatePaypalConfigState" dataType="paypal_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/payment/" method="POST"> <object key="groups" dataType="paypal_config_state"> <object key="paypal_alternative_payment_methods" dataType="paypal_config_state"> diff --git a/app/code/Magento/Paypal/Test/Mftf/Page/AdminConfigPaymentMethodsPage.xml b/app/code/Magento/Paypal/Test/Mftf/Page/AdminConfigPaymentMethodsPage.xml index 111e0d8b01a93..28e77e82d91f1 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Page/AdminConfigPaymentMethodsPage.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Page/AdminConfigPaymentMethodsPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminConfigPaymentMethodsPage" url="admin/system_config/edit/section/payment/" area="admin" module="Magento_Config"> <section name="OtherPaymentsConfigSection"/> </page> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/OtherPaymentsConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/OtherPaymentsConfigSection.xml index ec4f4f4ae09e8..bca6f963058e4 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/OtherPaymentsConfigSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/OtherPaymentsConfigSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="OtherPaymentsConfigSection"> <element name="expandedTab" type="button" selector="#payment_us_other_payment_methods-head.open"/> </section> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml index 283bc32412646..ac752e8412ff9 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml @@ -7,9 +7,12 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminConfigPaymentsSectionState"> <annotations> + <features value="PayPal"/> + <stories value="Payment methods"/> + <title value="Other Payment Methods section in Admin expanded by default"/> <description value="Other Payment Methods section in Admin expanded by default"/> <severity value="AVERAGE"/> <testCaseId value="MAGETWO-92043"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/composer.json b/app/code/Magento/Paypal/Test/Mftf/composer.json deleted file mode 100644 index 4822c694580ef..0000000000000 --- a/app/code/Magento/Paypal/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-paypal", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-instant-purchase": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout-agreements": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php index bd4da25cb84d0..44775d7e381bb 100644 --- a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Paypal\Test\Unit\Controller\Payflow; +use Magento\Framework\Event\ManagerInterface; use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Checkout\Block\Onepage\Success; use Magento\Checkout\Model\Session; @@ -96,6 +97,11 @@ class ReturnUrlTest extends \PHPUnit\Framework\TestCase */ private $paymentFailures; + /** + * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $eventManagerMock; + /** * @inheritdoc */ @@ -148,25 +154,31 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->context->expects($this->any())->method('getView')->willReturn($this->view); - $this->context->expects($this->any())->method('getRequest')->willReturn($this->request); - $this->paymentFailures = $this->getMockBuilder(PaymentFailuresInterface::class) ->disableOriginalConstructor() ->getMock(); + $this->eventManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->context->method('getView') ->willReturn($this->view); $this->context->method('getRequest') ->willReturn($this->request); - - $this->returnUrl = $this->objectManager->getObject(ReturnUrl::class, [ - 'context' => $this->context, - 'checkoutSession' => $this->checkoutSession, - 'orderFactory' => $this->orderFactory, - 'checkoutHelper' => $this->checkoutHelper, - 'paymentFailures' => $this->paymentFailures, - ]); + $this->context->method('getEventManager') + ->willReturn($this->eventManagerMock); + + $this->returnUrl = $this->objectManager->getObject( + ReturnUrl::class, + [ + 'context' => $this->context, + 'checkoutSession' => $this->checkoutSession, + 'orderFactory' => $this->orderFactory, + 'checkoutHelper' => $this->checkoutHelper, + 'paymentFailures' => $this->paymentFailures, + ] + ); } /** @@ -187,6 +199,10 @@ public function testExecuteAllowedOrderState($state) ->with('goto_success_page', true) ->willReturnSelf(); + $this->eventManagerMock->expects($this->once()) + ->method('dispatch') + ->with('paypal_checkout_success', $this->arrayHasKey('order')); + $result = $this->returnUrl->execute(); $this->assertNull($result); } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/CartTest.php b/app/code/Magento/Paypal/Test/Unit/Model/CartTest.php index 0f787f0f513a1..28837727533d2 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/CartTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/CartTest.php @@ -70,7 +70,7 @@ protected function setUp() public function testInvalidGetAllItems($items) { $taxContainer = new \Magento\Framework\DataObject( - ['base_discount_tax_compensation_amount' => 0.2, 'base_shipping_discount_tax_compensation_amnt' => 0.1] + ['base_discount_tax_compensation_amount' => 0.2, 'base_shipping_discount_tax_compensation_amount' => 0.1] ); $this->_salesModel->expects($this->once())->method('getTaxContainer')->will($this->returnValue($taxContainer)); $this->_salesModel->expects($this->once())->method('getAllItems')->will($this->returnValue($items)); @@ -146,7 +146,7 @@ public function testInvalidTotalsGetAllItems($values, $transferDiscount) $this->assertEquals( $values['base_tax_amount'] + $values['base_discount_tax_compensation_amount'] + - $values['base_shipping_discount_tax_compensation_amnt'], + $values['base_shipping_discount_tax_compensation_amount'], $this->_model->getTax() ); $this->assertEquals($values['base_shipping_amount'], $this->_model->getShipping()); @@ -162,7 +162,7 @@ public function invalidTotalsGetAllItemsDataProvider() [ [ 'base_discount_tax_compensation_amount' => 0, - 'base_shipping_discount_tax_compensation_amnt' => 0, + 'base_shipping_discount_tax_compensation_amount' => 0, 'base_subtotal' => 0, 'base_tax_amount' => 0, 'base_shipping_amount' => 0, @@ -174,7 +174,7 @@ public function invalidTotalsGetAllItemsDataProvider() [ [ 'base_discount_tax_compensation_amount' => 1, - 'base_shipping_discount_tax_compensation_amnt' => 2, + 'base_shipping_discount_tax_compensation_amount' => 2, 'base_subtotal' => 3, 'base_tax_amount' => 4, 'base_shipping_amount' => 5, @@ -255,8 +255,8 @@ protected function _prepareInvalidModelData($values, $transferDiscount) [ 'base_discount_tax_compensation_amount' => $values['base_discount_tax_compensation_amount'], - 'base_shipping_discount_tax_compensation_amnt' => - $values['base_shipping_discount_tax_compensation_amnt'], + 'base_shipping_discount_tax_compensation_amount' => + $values['base_shipping_discount_tax_compensation_amount'], ] ); $expectedSubtotal = $values['base_subtotal']; diff --git a/app/code/Magento/Paypal/composer.json b/app/code/Magento/Paypal/composer.json index 8bc05710452bb..b62985e8b8b70 100644 --- a/app/code/Magento/Paypal/composer.json +++ b/app/code/Magento/Paypal/composer.json @@ -30,7 +30,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index c1ff4c9b1c6ca..26ec9b3152e4b 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -77,7 +77,7 @@ <group id="configuration_details"> <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> </group> - <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> + <group id="paypal_payflow_required" showInDefault="1" showInWebsite="1" sortOrder="10"> <field id="enable_paypal_payflow"> <attribute type="shared">0</attribute> <config_path>payment/paypal_payment_pro/active</config_path> @@ -90,7 +90,7 @@ <label>Basic Settings - PayPal Payments Pro</label> </group> </group> - <group id="wps_express" extends="payment_all_paypal/express_checkout"> + <group id="wps_express" translate="label comment" extends="payment_all_paypal/express_checkout"> <label>Payments Standard</label> <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> @@ -110,7 +110,7 @@ <config_path>payment/wps_express_bml/active</config_path> </field> </group> - <group id="settings_ec"> + <group id="settings_ec" translate="label"> <label>Basic Settings - PayPal Website Payments Standard</label> </group> </group> @@ -124,7 +124,7 @@ <group id="payflow_link_us" extends="payment_all_paypal/payflow_link"/> </group> </section> - <section id="payment_gb" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> + <section id="payment_gb" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> <group id="paypal_alternative_payment_methods" sortOrder="5" showInDefault="0" showInWebsite="0" showInStore="0"> <group id="express_checkout_gb" translate="label comment" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> <label>PayPal Express Checkout</label> @@ -158,7 +158,7 @@ </group> </group> <include path="Magento_Paypal::system/payments_pro_hosted_solution_with_express_checkout.xml"/> - <group id="wps_express" extends="payment_all_paypal/express_checkout" sortOrder="50"> + <group id="wps_express" translate="label comment" extends="payment_all_paypal/express_checkout" sortOrder="50"> <label>Website Payments Standard</label> <comment>Accept credit card and PayPal payments securely.</comment> <attribute type="activity_path">payment/wps_express/active</attribute> @@ -166,7 +166,7 @@ <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> - <group id="express_checkout_required_express_checkout"> + <group id="express_checkout_required_express_checkout" translate="label"> <label>Website Payments Standard</label> </group> <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> @@ -178,7 +178,7 @@ <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> </group> - <group id="settings_ec"> + <group id="settings_ec" translate="label"> <label>Basic Settings - PayPal Website Payments Standard</label> </group> </group> @@ -227,7 +227,7 @@ <comment>Choose a secure bundled payment solution for your business.</comment> <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup&subTab=newlineup</help_url> <attribute type="displayIn">other_paypal_payment_solutions</attribute> - <group id="wps_other" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="wps_other" translate="label comment" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Website Payments Standard</label> <fieldset_css>complex</fieldset_css> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> @@ -237,7 +237,7 @@ <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> </group> <group id="express_checkout_required"> - <group id="express_checkout_required_express_checkout"> + <group id="express_checkout_required_express_checkout" translate="label"> <label>Website Payments Standard</label> </group> <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> @@ -271,7 +271,7 @@ <group id="paypal_group_all_in_one"> <group id="wps_other" sortOrder="20"/> </group> - <group id="paypal_payment_gateways" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="paypal_payment_gateways" translate="label" showInDefault="1" showInWebsite="1" showInStore="1"> <fieldset_css>complex paypal-other-section paypal-gateways-section</fieldset_css> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> <label><![CDATA[PayPal Payment Gateways <i>Process payments using your own internet merchant account.</i>]]></label> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 920c612021a1a..bff076aad9cb5 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -162,7 +162,7 @@ <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/paypal_express_bml/active</config_path> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -184,12 +184,12 @@ <group id="advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="30"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="10"> <label>Publisher ID</label> @@ -198,9 +198,8 @@ <attribute type="shared">1</attribute> </field> <field id="bml_wizard" translate="button_label" sortOrder="15" showInDefault="1" showInWebsite="1"> - <label></label> <button_label>Get Publisher ID from PayPal</button_label> - <button_url><![CDATA[https:/financing.paypal.com/ppfinportal/cart/index?dcp=4eff8563b9cc505e0b9afaff3256705081553c79]]></button_url> + <button_url><![CDATA[https://financing.paypal.com/ppfinportal/cart/index?dcp=4eff8563b9cc505e0b9afaff3256705081553c79]]></button_url> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\BmlApiWizard</frontend_model> </field> <group id="settings_bml_homepage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index 3012ff4361483..5eb596c9c4f45 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -100,7 +100,7 @@ <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml"> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index 60a4d670e8a18..d27dde02c579e 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -109,7 +109,7 @@ <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41"> <comment><![CDATA[Payflow Link lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> @@ -130,12 +130,12 @@ <group id="payflow_link_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="60"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml index 80ba1c3ac03a2..77acff48c247e 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml @@ -49,7 +49,7 @@ <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21"> <comment><![CDATA[Payments Pro Hosted Solution lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <requires> <field id="pphs_enable"/> @@ -58,12 +58,12 @@ <group id="pphs_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="22"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml index 90ffeddbb812e..6090025024dd7 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml @@ -34,7 +34,7 @@ <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -53,12 +53,12 @@ <group id="paypal_payflow_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="40"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> diff --git a/app/code/Magento/Paypal/etc/db_schema_whitelist.json b/app/code/Magento/Paypal/etc/db_schema_whitelist.json index 64aa8456b5432..8caacbf308232 100644 --- a/app/code/Magento/Paypal/etc/db_schema_whitelist.json +++ b/app/code/Magento/Paypal/etc/db_schema_whitelist.json @@ -1,120 +1,120 @@ { - "paypal_billing_agreement": { - "column": { - "agreement_id": true, - "customer_id": true, - "method_code": true, - "reference_id": true, - "status": true, - "created_at": true, - "updated_at": true, - "store_id": true, - "agreement_label": true + "paypal_billing_agreement": { + "column": { + "agreement_id": true, + "customer_id": true, + "method_code": true, + "reference_id": true, + "status": true, + "created_at": true, + "updated_at": true, + "store_id": true, + "agreement_label": true + }, + "index": { + "PAYPAL_BILLING_AGREEMENT_CUSTOMER_ID": true, + "PAYPAL_BILLING_AGREEMENT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "PAYPAL_BILLING_AGREEMENT_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "PAYPAL_BILLING_AGREEMENT_STORE_ID_STORE_STORE_ID": true + } }, - "index": { - "PAYPAL_BILLING_AGREEMENT_CUSTOMER_ID": true, - "PAYPAL_BILLING_AGREEMENT_STORE_ID": true + "paypal_billing_agreement_order": { + "column": { + "agreement_id": true, + "order_id": true + }, + "index": { + "PAYPAL_BILLING_AGREEMENT_ORDER_ORDER_ID": true + }, + "constraint": { + "PRIMARY": true, + "PAYPAL_BILLING_AGRT_ORDER_AGRT_ID_PAYPAL_BILLING_AGRT_AGRT_ID": true, + "PAYPAL_BILLING_AGREEMENT_ORDER_ORDER_ID_SALES_ORDER_ENTITY_ID": true + } }, - "constraint": { - "PRIMARY": true, - "PAYPAL_BILLING_AGREEMENT_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "PAYPAL_BILLING_AGREEMENT_STORE_ID_STORE_STORE_ID": true - } - }, - "paypal_billing_agreement_order": { - "column": { - "agreement_id": true, - "order_id": true - }, - "index": { - "PAYPAL_BILLING_AGREEMENT_ORDER_ORDER_ID": true - }, - "constraint": { - "PRIMARY": true, - "PAYPAL_BILLING_AGRT_ORDER_AGRT_ID_PAYPAL_BILLING_AGRT_AGRT_ID": true, - "PAYPAL_BILLING_AGREEMENT_ORDER_ORDER_ID_SALES_ORDER_ENTITY_ID": true - } - }, - "paypal_settlement_report": { - "column": { - "report_id": true, - "report_date": true, - "account_id": true, - "filename": true, - "last_modified": true - }, - "constraint": { - "PRIMARY": true, - "PAYPAL_SETTLEMENT_REPORT_REPORT_DATE_ACCOUNT_ID": true - } - }, - "paypal_settlement_report_row": { - "column": { - "row_id": true, - "report_id": true, - "transaction_id": true, - "invoice_id": true, - "paypal_reference_id": true, - "paypal_reference_id_type": true, - "transaction_event_code": true, - "transaction_initiation_date": true, - "transaction_completion_date": true, - "transaction_debit_or_credit": true, - "gross_transaction_amount": true, - "gross_transaction_currency": true, - "fee_debit_or_credit": true, - "fee_amount": true, - "fee_currency": true, - "custom_field": true, - "consumer_id": true, - "payment_tracking_id": true, - "store_id": true + "paypal_settlement_report": { + "column": { + "report_id": true, + "report_date": true, + "account_id": true, + "filename": true, + "last_modified": true + }, + "constraint": { + "PRIMARY": true, + "PAYPAL_SETTLEMENT_REPORT_REPORT_DATE_ACCOUNT_ID": true + } }, - "index": { - "PAYPAL_SETTLEMENT_REPORT_ROW_REPORT_ID": true + "paypal_settlement_report_row": { + "column": { + "row_id": true, + "report_id": true, + "transaction_id": true, + "invoice_id": true, + "paypal_reference_id": true, + "paypal_reference_id_type": true, + "transaction_event_code": true, + "transaction_initiation_date": true, + "transaction_completion_date": true, + "transaction_debit_or_credit": true, + "gross_transaction_amount": true, + "gross_transaction_currency": true, + "fee_debit_or_credit": true, + "fee_amount": true, + "fee_currency": true, + "custom_field": true, + "consumer_id": true, + "payment_tracking_id": true, + "store_id": true + }, + "index": { + "PAYPAL_SETTLEMENT_REPORT_ROW_REPORT_ID": true + }, + "constraint": { + "PRIMARY": true, + "FK_E183E488F593E0DE10C6EBFFEBAC9B55": true + } }, - "constraint": { - "PRIMARY": true, - "FK_E183E488F593E0DE10C6EBFFEBAC9B55": true - } - }, - "paypal_cert": { - "column": { - "cert_id": true, - "website_id": true, - "content": true, - "updated_at": true + "paypal_cert": { + "column": { + "cert_id": true, + "website_id": true, + "content": true, + "updated_at": true + }, + "index": { + "PAYPAL_CERT_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "PAYPAL_CERT_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } }, - "index": { - "PAYPAL_CERT_WEBSITE_ID": true + "paypal_payment_transaction": { + "column": { + "transaction_id": true, + "txn_id": true, + "additional_information": true, + "created_at": true + }, + "constraint": { + "PRIMARY": true, + "PAYPAL_PAYMENT_TRANSACTION_TXN_ID": true + } }, - "constraint": { - "PRIMARY": true, - "PAYPAL_CERT_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "paypal_payment_transaction": { - "column": { - "transaction_id": true, - "txn_id": true, - "additional_information": true, - "created_at": true + "quote_payment": { + "column": { + "paypal_payer_id": true, + "paypal_payer_status": true, + "paypal_correlation_id": true + } }, - "constraint": { - "PRIMARY": true, - "PAYPAL_PAYMENT_TRANSACTION_TXN_ID": true - } - }, - "quote_payment": { - "column": { - "paypal_payer_id": true, - "paypal_payer_status": true, - "paypal_correlation_id": true - } - }, - "sales_order": { - "column": { - "paypal_ipn_customer_notified": true + "sales_order": { + "column": { + "paypal_ipn_customer_notified": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index 797edbf8bfa1e..c3f3e38fa03b4 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -1,17 +1,17 @@ " - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’ s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. + Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. "," - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " payflowpro,"Payflow Pro" "Billing Agreements","Billing Agreements" @@ -484,27 +484,27 @@ Manage,Manage "Enable PayPal Credit","Enable PayPal Credit" "PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href=""https:/www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a> + <a href=""https://www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a> ","PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href=""https:/www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a> + <a href=""https://www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a> " "Sort Order PayPal Credit","Sort Order PayPal Credit" "Advertise PayPal Credit","Advertise PayPal Credit" " - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. "," - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " "Publisher ID","Publisher ID" "Required to display a banner","Required to display a banner" @@ -645,19 +645,19 @@ User,User "Accept payments with a PCI compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)","Accept payments with a PCI compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)" "Payments Pro Hosted Solution","Payments Pro Hosted Solution" " - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. "," - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " "Basic Settings - PayPal Payments Pro Hosted Solution","Basic Settings - PayPal Payments Pro Hosted Solution" "Display Express Checkout in the Payment Information step","Display Express Checkout in the Payment Information step" @@ -683,17 +683,17 @@ User,User "Card Security Code Does Not Match","Card Security Code Does Not Match" "Payflow Pro and Express Checkout","Payflow Pro and Express Checkout" " - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. "," - <a href=""https:/financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> + <a href=""https://financing.paypal.com/ppfinportal/content/whyUseFinancing"" target=""_blank"">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href=""https:/financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. + or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " diff --git a/app/code/Magento/Persistent/Test/Mftf/Data/PersistentData.xml b/app/code/Magento/Persistent/Test/Mftf/Data/PersistentData.xml index a23d1169b6865..f4e2fa198e7ff 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Data/PersistentData.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Data/PersistentData.xml @@ -6,7 +6,7 @@ */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="PersistentConfigDefault" type="persistent_config_state"> <requiredEntity type="persistent_options_enabled">persistentDefaultState</requiredEntity> </entity> diff --git a/app/code/Magento/Persistent/Test/Mftf/Metadata/persistent_config-meta.xml b/app/code/Magento/Persistent/Test/Mftf/Metadata/persistent_config-meta.xml index 42aae658b2e27..d165ca5f929b0 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Metadata/persistent_config-meta.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Metadata/persistent_config-meta.xml @@ -6,7 +6,7 @@ */ --> -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreatePersistentConfigState" dataType="persistent_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/persistent/" method="POST"> <object key="groups" dataType="persistent_config_state"> <object key="options" dataType="persistent_config_state"> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml index 49d07992694d4..a383fd4505bd6 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="GuestCheckoutWithEnabledPersistentTest"> <annotations> <features value="Persistent"/> + <stories value="Guest checkout"/> <title value="Guest Checkout with Enabled Persistent"/> <description value="Checkout data must be restored after page checkout reload."/> <severity value="CRITICAL"/> @@ -72,12 +73,12 @@ <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seeBilllingPostcode" /> <see selector="{{CheckoutPaymentSection.billingAddress}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seeBilllingTelephone" /> <!-- Check that "Ship To" block contains correct information --> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.firstName}}" stepKey="seeShipToFirstName" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.lastName}}" stepKey="seeShipToLastName" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeShipToStreet" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.city}}" stepKey="seeShipToCity" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.state}}" stepKey="seeShipToState" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seeShipToPostcode" /> - <see selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seeShipToTelephone" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.firstName}}" stepKey="seeShipToFirstName" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.lastName}}" stepKey="seeShipToLastName" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="seeShipToStreet" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.city}}" stepKey="seeShipToCity" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.state}}" stepKey="seeShipToState" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="seeShipToPostcode" /> + <see selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="seeShipToTelephone" /> </test> </tests> diff --git a/app/code/Magento/Persistent/Test/Mftf/composer.json b/app/code/Magento/Persistent/Test/Mftf/composer.json deleted file mode 100644 index 0ea42b2755a0d..0000000000000 --- a/app/code/Magento/Persistent/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-persistent", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Persistent/etc/db_schema_whitelist.json b/app/code/Magento/Persistent/etc/db_schema_whitelist.json index 93da6b440faa6..50a4567d4a6f1 100644 --- a/app/code/Magento/Persistent/etc/db_schema_whitelist.json +++ b/app/code/Magento/Persistent/etc/db_schema_whitelist.json @@ -1,27 +1,27 @@ { - "persistent_session": { - "column": { - "persistent_id": true, - "key": true, - "customer_id": true, - "website_id": true, - "info": true, - "updated_at": true + "persistent_session": { + "column": { + "persistent_id": true, + "key": true, + "customer_id": true, + "website_id": true, + "info": true, + "updated_at": true + }, + "index": { + "PERSISTENT_SESSION_UPDATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "PERSISTENT_SESSION_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "PERSISTENT_SESSION_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "PERSISTENT_SESSION_KEY": true, + "PERSISTENT_SESSION_CUSTOMER_ID": true + } }, - "index": { - "PERSISTENT_SESSION_UPDATED_AT": true - }, - "constraint": { - "PRIMARY": true, - "PERSISTENT_SESSION_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "PERSISTENT_SESSION_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "PERSISTENT_SESSION_KEY": true, - "PERSISTENT_SESSION_CUSTOMER_ID": true - } - }, - "quote": { - "column": { - "is_persistent": true + "quote": { + "column": { + "is_persistent": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/ProductAlert/Block/Email/Price.php b/app/code/Magento/ProductAlert/Block/Email/Price.php index 982b0f7f63375..0430a21dc8bfd 100644 --- a/app/code/Magento/ProductAlert/Block/Email/Price.php +++ b/app/code/Magento/ProductAlert/Block/Email/Price.php @@ -15,7 +15,7 @@ class Price extends \Magento\ProductAlert\Block\Email\AbstractEmail /** * @var string */ - protected $_template = 'email/price.phtml'; + protected $_template = 'Magento_ProductAlert::email/price.phtml'; /** * Retrieve unsubscribe url for product diff --git a/app/code/Magento/ProductAlert/Block/Email/Stock.php b/app/code/Magento/ProductAlert/Block/Email/Stock.php index f424e7d7125c4..d01960b8eb855 100644 --- a/app/code/Magento/ProductAlert/Block/Email/Stock.php +++ b/app/code/Magento/ProductAlert/Block/Email/Stock.php @@ -15,7 +15,7 @@ class Stock extends \Magento\ProductAlert\Block\Email\AbstractEmail /** * @var string */ - protected $_template = 'email/stock.phtml'; + protected $_template = 'Magento_ProductAlert::email/stock.phtml'; /** * Retrieve unsubscribe url for product diff --git a/app/code/Magento/ProductAlert/Test/Mftf/composer.json b/app/code/Magento/ProductAlert/Test/Mftf/composer.json deleted file mode 100644 index 37fda5965a0f8..0000000000000 --- a/app/code/Magento/ProductAlert/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-product-alert", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ProductAlert/etc/db_schema_whitelist.json b/app/code/Magento/ProductAlert/etc/db_schema_whitelist.json index 31dd3857dff4e..266d00e04c5bc 100644 --- a/app/code/Magento/ProductAlert/etc/db_schema_whitelist.json +++ b/app/code/Magento/ProductAlert/etc/db_schema_whitelist.json @@ -1,51 +1,51 @@ { - "product_alert_price": { - "column": { - "alert_price_id": true, - "customer_id": true, - "product_id": true, - "price": true, - "website_id": true, - "add_date": true, - "last_send_date": true, - "send_count": true, - "status": true + "product_alert_price": { + "column": { + "alert_price_id": true, + "customer_id": true, + "product_id": true, + "price": true, + "website_id": true, + "add_date": true, + "last_send_date": true, + "send_count": true, + "status": true + }, + "index": { + "PRODUCT_ALERT_PRICE_CUSTOMER_ID": true, + "PRODUCT_ALERT_PRICE_PRODUCT_ID": true, + "PRODUCT_ALERT_PRICE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "PRODUCT_ALERT_PRICE_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "PRODUCT_ALERT_PRICE_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "PRODUCT_ALERT_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "PRODUCT_ALERT_PRICE_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true + } }, - "index": { - "PRODUCT_ALERT_PRICE_CUSTOMER_ID": true, - "PRODUCT_ALERT_PRICE_PRODUCT_ID": true, - "PRODUCT_ALERT_PRICE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "PRODUCT_ALERT_PRICE_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "PRODUCT_ALERT_PRICE_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "PRODUCT_ALERT_PRICE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "PRODUCT_ALERT_PRICE_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true - } - }, - "product_alert_stock": { - "column": { - "alert_stock_id": true, - "customer_id": true, - "product_id": true, - "website_id": true, - "add_date": true, - "send_date": true, - "send_count": true, - "status": true - }, - "index": { - "PRODUCT_ALERT_STOCK_CUSTOMER_ID": true, - "PRODUCT_ALERT_STOCK_PRODUCT_ID": true, - "PRODUCT_ALERT_STOCK_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "PRODUCT_ALERT_STOCK_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "PRODUCT_ALERT_STOCK_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "PRODUCT_ALERT_STOCK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "PRODUCT_ALERT_STOCK_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true + "product_alert_stock": { + "column": { + "alert_stock_id": true, + "customer_id": true, + "product_id": true, + "website_id": true, + "add_date": true, + "send_date": true, + "send_count": true, + "status": true + }, + "index": { + "PRODUCT_ALERT_STOCK_CUSTOMER_ID": true, + "PRODUCT_ALERT_STOCK_PRODUCT_ID": true, + "PRODUCT_ALERT_STOCK_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "PRODUCT_ALERT_STOCK_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "PRODUCT_ALERT_STOCK_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "PRODUCT_ALERT_STOCK_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "PRODUCT_ALERT_STOCK_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php b/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php index 2277aa980f66c..45c4925640a0c 100644 --- a/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php +++ b/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php @@ -9,9 +9,8 @@ * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\ProductVideo\Block\Product\View; -use Magento\Catalog\Model\Product\Image\UrlBuilder; +namespace Magento\ProductVideo\Block\Product\View; /** * @api @@ -93,6 +92,6 @@ public function getVideoSettingsJson() */ public function getOptionsMediaGalleryDataJson() { - return $this->jsonEncoder->encode([]); + return $this->jsonEncoder->encode([]); } } diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php index ab3c8f3c5269a..ce1493b349a85 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery; use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; @@ -29,6 +30,7 @@ public function beforeExecute( \Magento\Catalog\Model\Product $product, array $arguments = [] ) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ $attribute = $mediaGalleryCreateHandler->getAttribute(); $mediaCollection = $this->getMediaEntriesDataCollection($product, $attribute); if (!empty($mediaCollection)) { @@ -36,7 +38,7 @@ public function beforeExecute( $mediaCollection = $this->addAdditionalStoreData($mediaCollection, $storeDataCollection); $product->setData( $attribute->getAttributeCode(), - $mediaCollection + $product->getData($attribute->getAttributeCode()) + $mediaCollection ); } } @@ -56,6 +58,9 @@ public function afterExecute( ); if (!empty($mediaCollection)) { + $newVideoCollection = $this->collectNewVideos($mediaCollection); + $this->saveVideoData($newVideoCollection, 0); + $videoDataCollection = $this->collectVideoData($mediaCollection); $this->saveVideoData($videoDataCollection, $product->getStoreId()); $this->saveAdditionalStoreData($videoDataCollection); @@ -167,10 +172,7 @@ protected function collectVideoData(array $mediaCollection) { $videoDataCollection = []; foreach ($mediaCollection as $item) { - if (!empty($item['media_type']) - && empty($item['removed']) - && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - ) { + if ($this->isVideoItem($item)) { $videoData = $this->extractVideoDataFromRowData($item); $videoDataCollection[] = $videoData; } @@ -199,11 +201,7 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection { $ids = []; foreach ($mediaCollection as $item) { - if (!empty($item['media_type']) - && empty($item['removed']) - && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - && isset($item['save_data_from']) - ) { + if ($this->isVideoItem($item) && isset($item['save_data_from'])) { $ids[] = $item['save_data_from']; } } @@ -215,18 +213,19 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection * @param array $data * @return array */ - protected function addAdditionalStoreData(array $mediaCollection, array $data) + protected function addAdditionalStoreData(array $mediaCollection, array $data): array { - foreach ($mediaCollection as &$mediaItem) { + $return = []; + foreach ($mediaCollection as $key => $mediaItem) { if (!empty($mediaItem['save_data_from'])) { $additionalData = $this->createAdditionalStoreDataCollection($data, $mediaItem['save_data_from']); if (!empty($additionalData)) { $mediaItem[self::ADDITIONAL_STORE_DATA_KEY] = $additionalData; } } + $return[$key] = $mediaItem; } - - return ['images' => $mediaCollection]; + return ['images' => $return]; } /** @@ -234,7 +233,7 @@ protected function addAdditionalStoreData(array $mediaCollection, array $data) * @param int $valueId * @return array */ - protected function createAdditionalStoreDataCollection(array $storeData, $valueId) + protected function createAdditionalStoreDataCollection(array $storeData, $valueId): array { $result = []; foreach ($storeData as $item) { @@ -246,4 +245,41 @@ protected function createAdditionalStoreDataCollection(array $storeData, $valueI return $result; } + + /** + * @param array $mediaCollection + * @return array + */ + private function collectNewVideos(array $mediaCollection): array + { + $return = []; + foreach ($mediaCollection as $item) { + if ($this->isVideoItem($item) && $this->isNewVideo($item)) { + $return[] = $this->extractVideoDataFromRowData($item); + } + } + return $return; + } + + /** + * @param array $item + * @return bool + */ + private function isVideoItem(array $item): bool + { + return !empty($item['media_type']) + && empty($item['removed']) + && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE; + } + + /** + * @param array $item + * @return bool + */ + private function isNewVideo(array $item): bool + { + return !isset($item['video_url_default'], $item['video_title_default']) + || empty($item['video_url_default']) + || empty($item['video_title_default']); + } } diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php index 6c534580c39d9..31d63efcf8cb0 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery; use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; @@ -55,8 +56,8 @@ protected function collectVideoEntriesIds(array $mediaCollection) { $ids = []; foreach ($mediaCollection as $item) { - if ($item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - && !array_key_exists('video_url', $item) + if ($item['media_type'] === ExternalVideoEntryConverter::MEDIA_TYPE_CODE + && !isset($item['video_url']) ) { $ids[] = $item['value_id']; } @@ -72,7 +73,7 @@ protected function collectVideoEntriesIds(array $mediaCollection) protected function loadVideoDataById(array $ids, $storeId = null) { $mainTableAlias = $this->resourceModel->getMainTableAlias(); - $joinConditions = $mainTableAlias.'.value_id = store_value.value_id'; + $joinConditions = $mainTableAlias . '.value_id = store_value.value_id'; if (null !== $storeId) { $joinConditions = implode( ' AND ', @@ -138,10 +139,10 @@ protected function addVideoDataToMediaEntries(array $mediaCollection, array $dat protected function substituteNullsWithDefaultValues(array $rowData) { foreach ($this->getVideoProperties(false) as $key) { - if (empty($rowData[$key]) && !empty($rowData[$key.'_default'])) { - $rowData[$key] = $rowData[$key.'_default']; + if (empty($rowData[$key]) && !empty($rowData[$key . '_default'])) { + $rowData[$key] = $rowData[$key . '_default']; } - unset($rowData[$key.'_default']); + unset($rowData[$key . '_default']); } return $rowData; @@ -154,8 +155,7 @@ protected function substituteNullsWithDefaultValues(array $rowData) protected function getVideoProperties($withDbMapping = true) { $properties = $this->videoPropertiesDbMapping; - unset($properties['value_id']); - unset($properties['store_id']); + unset($properties['value_id'], $properties['store_id']); return $withDbMapping ? $properties : array_keys($properties); } diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminProductVideoActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminProductVideoActionGroup.xml index f28a9f9359def..1ef18d5f34ecd 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminProductVideoActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminProductVideoActionGroup.xml @@ -6,12 +6,13 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Add video in Admin Product page --> <actionGroup name="addProductVideo"> <arguments> <argument name="video" defaultValue="mftfTestProductVideo"/> </arguments> + <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductVideoSection"/> <waitForElementVisible selector="{{AdminProductImagesSection.addVideoButton}}" stepKey="waitForAddVideoButtonVisible" time="30"/> <click selector="{{AdminProductImagesSection.addVideoButton}}" stepKey="addVideo"/> @@ -27,6 +28,7 @@ <!-- Remove video in Admin Product page --> <actionGroup name="removeProductVideo"> + <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductVideoSection"/> <waitForElementVisible selector="{{AdminProductImagesSection.addVideoButton}}" stepKey="waitForAddVideoButtonVisible" time="30"/> <click selector="{{AdminProductImagesSection.removeVideoButton}}" stepKey="removeVideo"/> @@ -37,6 +39,7 @@ <arguments> <argument name="video" defaultValue="mftfTestProductVideo"/> </arguments> + <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductVideoSection"/> <waitForPageLoad stepKey="waitForPageLoad"/> <seeElement selector="{{AdminProductImagesSection.videoTitleText(video.videoShortTitle)}}" stepKey="seeVideoTitle"/> @@ -48,6 +51,7 @@ <arguments> <argument name="video" defaultValue="mftfTestProductVideo"/> </arguments> + <scrollTo selector="{{AdminProductImagesSection.productImagesToggle}}" x="0" y="-100" stepKey="scrollToArea"/> <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductVideoSection"/> <waitForPageLoad stepKey="waitForPageLoad"/> <dontSeeElement selector="{{AdminProductImagesSection.videoTitleText(video.videoShortTitle)}}" stepKey="seeVideoTitle"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/StorefrontProductVideoActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/StorefrontProductVideoActionGroup.xml index 1f7750ab1f2cf..8f0d41f8c2153 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/StorefrontProductVideoActionGroup.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/StorefrontProductVideoActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Assert product video in Storefront Product page --> <actionGroup name="assertProductVideoStorefrontProductPage"> <arguments> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml index b21ac404711f7..8fe5899e91ef8 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoConfigData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- mftf test youtube api key configuration --> <entity name="ProductVideoYoutubeApiKeyConfig" type="product_video_config"> <requiredEntity type="youtube_api_key_config">YouTubeApiKey</requiredEntity> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml index 93d3f93ff429f..5bc4ad86e0f06 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Data/ProductVideoData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="mftfTestProductVideo" type="product_video"> <data key="videoUrl">https://youtu.be/bpOSxM0rNPM</data> <data key="videoTitle">Arctic Monkeys - Do I Wanna Know? (Official Video)</data> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Metadata/product_video_config-meta.xml b/app/code/Magento/ProductVideo/Test/Mftf/Metadata/product_video_config-meta.xml index 07d91bc0a1e84..2525c3a3d0ff6 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Metadata/product_video_config-meta.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Metadata/product_video_config-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductVideoYouTubeApiKeyConfig" dataType="product_video_config" type="create" auth="adminFormKey" url="admin/system_config/save/section/catalog/" method="POST"> <object key="groups" dataType="product_video_config"> <object key="product_video" dataType="product_video_config"> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductImagesSection.xml index b232d24c51ee1..5db86267f7d7b 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductImagesSection.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductImagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductImagesSection"> <element name="addVideoButton" type="button" selector="#add_video_button" timeout="60"/> <element name="removeVideoButton" type="button" selector="//*[@id='media_gallery_content']//button[@class='action-remove']" timeout="60"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml index 89e6fd37b171b..71dad6a24f148 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductNewVideoSection"> <element name="saveButton" type="button" selector=".action-primary.video-create-button" timeout="30"/> <element name="saveButtonDisabled" type="text" selector="//button[@class='action-primary video-create-button' and @disabled='disabled']"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 03e1d91df4d3e..564122f71b9f4 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="productVideo" type="text" selector="//*[@class='product-video' and @data-type='{{videoType}}']" parameterized="true"/> </section> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml new file mode 100644 index 0000000000000..bd7cc0cdf5b4a --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminAddDefaultVideoSimpleProductTest"> + <annotations> + <group value="ProductVideo"/> + </annotations> + <before> + <!-- Set product video Youtube api key configuration --> + <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setStoreConfig" after="loginAsAdmin"/> + </before> + <after> + <!-- Set product video configuration to default --> + <createData entity="DefaultProductVideoConfig" stepKey="setStoreDefaultConfig" before="amOnLogoutPage"/> + </after> + + <!-- Add product video --> + <actionGroup ref="addProductVideo" stepKey="addProductVideo" after="fillMainProductForm"/> + + <!-- Assert product video in admin product form --> + <actionGroup ref="assertProductVideoAdminProductPage" stepKey="assertProductVideoAdminProductPage" after="saveProductForm"/> + + <!-- Assert product video in storefront product page --> + <actionGroup ref="assertProductVideoStorefrontProductPage" stepKey="assertProductVideoStorefrontProductPage" after="AssertProductInStorefrontProductPage"/> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml new file mode 100644 index 0000000000000..f5a7886fed45c --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveDefaultVideoSimpleProductTest"> + <annotations> + <group value="ProductVideo"/> + </annotations> + <before> + <!-- Set product video Youtube api key configuration --> + <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setStoreConfig" after="loginAsAdmin"/> + </before> + <after> + <!-- Set product video configuration to default --> + <createData entity="DefaultProductVideoConfig" stepKey="setStoreDefaultConfig" before="amOnLogoutPage"/> + </after> + + <!-- Add product video --> + <actionGroup ref="addProductVideo" stepKey="addProductVideo" after="fillMainProductForm"/> + + <!-- Remove product video --> + <actionGroup ref="removeProductVideo" stepKey="removeProductVideo" after="saveProductForm"/> + + <!-- Assert product video not in admin product form --> + <actionGroup ref="assertProductVideoNotInAdminProductPage" stepKey="assertProductVideoNotInAdminProductPage" after="saveProductFormAfterRemove"/> + + <!-- Assert product video not in storefront product page --> + <actionGroup ref="assertProductVideoNotInStorefrontProductPage" stepKey="assertProductVideoNotInStorefrontProductPage" after="AssertProductInStorefrontProductPage"/> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/composer.json b/app/code/Magento/ProductVideo/Test/Mftf/composer.json deleted file mode 100644 index a2f1223db010d..0000000000000 --- a/app/code/Magento/ProductVideo/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-product-video", - "description": "Add Video to Products", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php index 5770ea8b5689d..57ad71997dad7 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Test\Unit\Model\Plugin\Catalog\Product\Gallery; /** @@ -37,6 +38,9 @@ class CreateHandlerTest extends \PHPUnit\Framework\TestCase */ protected $mediaGalleryCreateHandler; + /** + * {@inheritDoc} + */ protected function setUp() { $this->product = $this->createMock(\Magento\Catalog\Model\Product::class); @@ -62,72 +66,18 @@ protected function setUp() ); } - public function testAfterExecute() + /** + * @dataProvider provideImageForAfterExecute + * @param array $image + * @param array $expectedSave + * @param int $rowSaved + */ + public function testAfterExecute($image, $expectedSave, $rowSaved): void { - $mediaData = [ - 'images' => [ - '72mljfhmasfilp9cuq' => [ - 'position' => '3', - 'media_type' => 'external-video', - 'file' => '/i/n/index111111.jpg', - 'value_id' => '4', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_provider' => 'youtube', - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'Some second title', - 'video_description' => 'Description second', - 'video_metadata' => 'meta two', - 'role' => '', - ], - 'w596fi79hv1p6wj21u' => [ - 'position' => '4', - 'media_type' => 'image', - 'video_provider' => '', - 'file' => '/h/d/hd_image.jpg', - 'value_id' => '7', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_url' => '', - 'video_title' => '', - 'video_description' => '', - 'video_metadata' => '', - 'role' => '', - ], - 'tcodwd7e0dirifr64j' => [ - 'position' => '4', - 'media_type' => 'external-video', - 'file' => '/s/a/sample_3.jpg', - 'value_id' => '5', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_provider' => 'youtube', - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'Some second title', - 'video_description' => 'Description second', - 'video_metadata' => 'meta two', - 'role' => '', - 'additional_store_data' => [ - 0 => [ - 'store_id' => '0', - 'video_provider' => null, - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'New Title', - 'video_description' => 'New Description', - 'video_metadata' => 'New metadata', - ], - ] - ], - ], - ]; - $this->product->expects($this->once()) ->method('getData') ->with('media_gallery') - ->willReturn($mediaData); + ->willReturn(['images' => $image]); $this->product->expects($this->once()) ->method('getStoreId') ->willReturn(0); @@ -136,13 +86,150 @@ public function testAfterExecute() ->method('getAttribute') ->willReturn($this->attribute); - $this->subject->afterExecute( - $this->mediaGalleryCreateHandler, - $this->product - ); + $this->resourceModel->expects($this->exactly($rowSaved)) + ->method('saveDataRow') + ->with('catalog_product_entity_media_gallery_value_video', $expectedSave) + ->willReturn(1); + + $this->subject->afterExecute($this->mediaGalleryCreateHandler, $this->product); + } + + /** + * DataProvider for testAfterExecute + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function provideImageForAfterExecute(): array + { + return [ + 'new_video' => [ + [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + ], + [ + 'value_id' => '4', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 2 + ], + 'image' => [ + [ + 'w596fi79hv1p6wj21u' => [ + 'position' => '4', + 'media_type' => 'image', + 'video_provider' => '', + 'file' => '/h/d/hd_image.jpg', + 'value_id' => '7', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_url' => '', + 'video_title' => '', + 'video_description' => '', + 'video_metadata' => '', + 'role' => '', + ], + ], + [], + 0 + ], + 'new_video_with_additional_data' => [ + [ + 'tcodwd7e0dirifr64j' => [ + 'position' => '4', + 'media_type' => 'external-video', + 'file' => '/s/a/sample_3.jpg', + 'value_id' => '5', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + 'additional_store_data' => [ + 0 => [ + 'store_id' => 0, + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + ], + ] + ], + ], + [ + 'value_id' => '5', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 3 + ], + 'not_new_video' => [ + [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_title_default' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + ], + [ + 'value_id' => '4', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 1 + ], + ]; } - public function testAfterExecuteEmpty() + /** + * Tests empty media gallery + */ + public function testAfterExecuteEmpty(): void { $this->product->expects($this->once()) ->method('getData') @@ -162,7 +249,7 @@ public function testAfterExecuteEmpty() /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testBeforeExecute() + public function testBeforeExecute(): void { $mediaData = [ 'images' => [ diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index 829816321c1a5..24497f891db43 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -20,7 +20,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/ProductVideo/etc/db_schema_whitelist.json b/app/code/Magento/ProductVideo/etc/db_schema_whitelist.json index b0c73baecd077..78fa6538da07c 100644 --- a/app/code/Magento/ProductVideo/etc/db_schema_whitelist.json +++ b/app/code/Magento/ProductVideo/etc/db_schema_whitelist.json @@ -1,18 +1,18 @@ { - "catalog_product_entity_media_gallery_value_video": { - "column": { - "value_id": true, - "store_id": true, - "provider": true, - "url": true, - "title": true, - "description": true, - "metadata": true - }, - "constraint": { - "FK_6FDF205946906B0E653E60AA769899F8": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_VIDEO_STORE_ID_STORE_STORE_ID": true, - "CAT_PRD_ENTT_MDA_GLR_VAL_VIDEO_VAL_ID_STORE_ID": true + "catalog_product_entity_media_gallery_value_video": { + "column": { + "value_id": true, + "store_id": true, + "provider": true, + "url": true, + "title": true, + "description": true, + "metadata": true + }, + "constraint": { + "FK_6FDF205946906B0E653E60AA769899F8": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_VIDEO_STORE_ID_STORE_STORE_ID": true, + "CAT_PRD_ENTT_MDA_GLR_VAL_VIDEO_VAL_ID_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/en_US.csv b/app/code/Magento/ProductVideo/i18n/en_US.csv index 3277843424bcf..2d226c6daefa3 100644 --- a/app/code/Magento/ProductVideo/i18n/en_US.csv +++ b/app/code/Magento/ProductVideo/i18n/en_US.csv @@ -40,4 +40,3 @@ Delete,Delete "Autostart base video","Autostart base video" "Show related video","Show related video" "Auto restart video","Auto restart video" -"Images And Videos","Images And Videos" diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js index 13b0e43a84d81..653434f1008ca 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js @@ -86,6 +86,7 @@ define([ this._height = this.element.data('height'); this._autoplay = !!this.element.data('autoplay'); this._playing = this._autoplay || false; + this.useYoutubeNocookie = this.element.data('youtubenocookie') || false; this._responsive = this.element.data('responsive') !== false; @@ -163,6 +164,12 @@ define([ * @private */ 'youtubeapiready': function () { + var host = 'https://www.youtube.com'; + + if (self.useYoutubeNocookie) { + host = 'https://www.youtube-nocookie.com'; + } + if (self._player !== undefined) { return; } @@ -177,6 +184,7 @@ define([ width: self._width, videoId: self._code, playerVars: self._params, + host: host, events: { /** @@ -469,7 +477,8 @@ define([ description: tmp.snippet.description, thumbnail: tmp.snippet.thumbnails.high.url, videoId: videoInfo.id, - videoProvider: videoInfo.type + videoProvider: videoInfo.type, + useYoutubeNocookie: videoInfo.useYoutubeNocookie }; this._videoInformation = respData; this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData); @@ -600,7 +609,8 @@ define([ var id, type, ampersandPosition, - vimeoRegex; + vimeoRegex, + useYoutubeNocookie = false; if (typeof href !== 'string') { return href; @@ -620,9 +630,13 @@ define([ id = id.substring(0, ampersandPosition); } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; + + if (href.host.match(/youtube-nocookie.com/)) { + useYoutubeNocookie = true; + } } else if (href.host.match(/vimeo\.com/)) { type = 'vimeo'; vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)', @@ -640,7 +654,7 @@ define([ } return id ? { - id: id, type: type, s: href.search.replace(/^\?/, '') + id: id, type: type, s: href.search.replace(/^\?/, ''), useYoutubeNocookie: useYoutubeNocookie } : false; } }); diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js index 1ab10c95a51bc..287c88e8a796f 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js @@ -21,6 +21,7 @@ define([ container: '.video-player-container', videoClass: 'product-video', reset: false, + useYoutubeNocookie: false, metaData: { DOM: { title: '.video-information.title span', @@ -87,13 +88,17 @@ define([ */ _doUpdate: function () { this.reset(); - this.element.find(this.options.container).append('<div class="' + - this.options.videoClass + - '" data-type="' + - this.options.videoProvider + - '" data-code="' + - this.options.videoId + - '" data-width="100%" data-height="100%"></div>'); + this.element.find(this.options.container).append( + '<div class="' + + this.options.videoClass + + '" data-type="' + + this.options.videoProvider + + '" data-code="' + + this.options.videoId + + '" data-youtubenocookie="' + + this.options.useYoutubeNocookie + + '" data-width="100%" data-height="100%"></div>' + ); this.element.find(this.options.metaData.DOM.wrapper).show(); this.element.find(this.options.metaData.DOM.title).text(this.options.metaData.data.title); this.element.find(this.options.metaData.DOM.uploaded).text(this.options.metaData.data.uploaded); @@ -337,6 +342,7 @@ define([ .createVideoPlayer({ videoId: data.videoId, videoProvider: data.videoProvider, + useYoutubeNocookie: data.useYoutubeNocookie, reset: false, metaData: { DOM: { diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index 9bb4b9996e3ad..82a7f2d1a8e19 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -34,7 +34,8 @@ define([ var id, type, ampersandPosition, - vimeoRegex; + vimeoRegex, + useYoutubeNocookie = false; /** * Get youtube ID @@ -68,9 +69,13 @@ define([ id = _getYoutubeId(id); type = 'youtube'; } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; + + if (href.host.match(/youtube-nocookie.com/)) { + useYoutubeNocookie = true; + } } else if (href.host.match(/vimeo\.com/)) { type = 'vimeo'; vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)', @@ -85,7 +90,7 @@ define([ } return id ? { - id: id, type: type, s: href.search.replace(/^\?/, '') + id: id, type: type, s: href.search.replace(/^\?/, ''), useYoutubeNocookie: useYoutubeNocookie } : false; } @@ -281,6 +286,7 @@ define([ tmpVideoData.id = dataUrl.id; tmpVideoData.provider = dataUrl.type; tmpVideoData.videoUrl = tmpInputData.videoUrl; + tmpVideoData.useYoutubeNocookie = dataUrl.useYoutubeNocookie; } videoData.push(tmpVideoData); @@ -629,6 +635,8 @@ define([ videoData.provider + '" data-code="' + videoData.id + + '" data-youtubenocookie="' + + videoData.useYoutubeNocookie + '" data-width="100%" data-height="100%"></div>' ); }, diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js index 3519d538e523a..75a2c1d75da15 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js @@ -88,6 +88,7 @@ define(['jquery', 'jquery/ui'], function ($) { this._playing = this._autoplay || false; this._loop = this.element.data('loop'); this._rel = this.element.data('related'); + this.useYoutubeNocookie = this.element.data('youtubenocookie') || false; this._responsive = this.element.data('responsive') !== false; @@ -164,6 +165,12 @@ define(['jquery', 'jquery/ui'], function ($) { * Handle event */ 'youtubeapiready': function () { + var host = 'https://www.youtube.com'; + + if (self.useYoutubeNocookie) { + host = 'https://www.youtube-nocookie.com'; + } + if (self._player !== undefined) { return; } @@ -182,6 +189,7 @@ define(['jquery', 'jquery/ui'], function ($) { width: self._width, videoId: self._code, playerVars: self._params, + host: host, events: { /** diff --git a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php index 9fe69b691424d..e18ab8587fc71 100644 --- a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php +++ b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php @@ -10,6 +10,7 @@ use Magento\Quote\Api\CartTotalRepositoryInterface; use Magento\Catalog\Helper\Product\ConfigurationPool; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Quote\Model\Cart\Totals\ItemConverter; use Magento\Quote\Api\CouponManagementInterface; @@ -94,6 +95,7 @@ public function get($cartId) $addressTotalsData = $quote->getShippingAddress()->getData(); $addressTotals = $quote->getShippingAddress()->getTotals(); } + unset($addressTotalsData[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]); /** @var \Magento\Quote\Api\Data\TotalsInterface $quoteTotals */ $quoteTotals = $this->totalsFactory->create(); diff --git a/app/code/Magento/Quote/Model/CouponManagement.php b/app/code/Magento/Quote/Model/CouponManagement.php index 62515a17f268b..55c21c974d6dd 100644 --- a/app/code/Magento/Quote/Model/CouponManagement.php +++ b/app/code/Magento/Quote/Model/CouponManagement.php @@ -55,6 +55,9 @@ public function set($cartId, $couponCode) if (!$quote->getItemsCount()) { throw new NoSuchEntityException(__('The "%1" Cart doesn\'t contain products.', $cartId)); } + if (!$quote->getStoreId()) { + throw new NoSuchEntityException(__('Cart isn\'t assigned to correct store')); + } $quote->getShippingAddress()->setCollectShippingRates(true); try { diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 3b19c6495dd99..052e1c2bc3805 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -526,7 +526,7 @@ public function getCurrency() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCurrency(\Magento\Quote\Api\Data\CurrencyInterface $currency = null) { @@ -534,7 +534,7 @@ public function setCurrency(\Magento\Quote\Api\Data\CurrencyInterface $currency } /** - * {@inheritdoc} + * @inheritdoc */ public function getItems() { @@ -542,7 +542,7 @@ public function getItems() } /** - * {@inheritdoc} + * @inheritdoc */ public function setItems(array $items = null) { @@ -550,7 +550,7 @@ public function setItems(array $items = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCreatedAt() { @@ -558,7 +558,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCreatedAt($createdAt) { @@ -566,7 +566,7 @@ public function setCreatedAt($createdAt) } /** - * {@inheritdoc} + * @inheritdoc */ public function getUpdatedAt() { @@ -574,7 +574,7 @@ public function getUpdatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setUpdatedAt($updatedAt) { @@ -582,7 +582,7 @@ public function setUpdatedAt($updatedAt) } /** - * {@inheritdoc} + * @inheritdoc */ public function getConvertedAt() { @@ -590,7 +590,7 @@ public function getConvertedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setConvertedAt($convertedAt) { @@ -598,7 +598,7 @@ public function setConvertedAt($convertedAt) } /** - * {@inheritdoc} + * @inheritdoc */ public function getIsActive() { @@ -606,7 +606,7 @@ public function getIsActive() } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsActive($isActive) { @@ -614,7 +614,7 @@ public function setIsActive($isActive) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIsVirtual($isVirtual) { @@ -622,7 +622,7 @@ public function setIsVirtual($isVirtual) } /** - * {@inheritdoc} + * @inheritdoc */ public function getItemsCount() { @@ -630,7 +630,7 @@ public function getItemsCount() } /** - * {@inheritdoc} + * @inheritdoc */ public function setItemsCount($itemsCount) { @@ -638,7 +638,7 @@ public function setItemsCount($itemsCount) } /** - * {@inheritdoc} + * @inheritdoc */ public function getItemsQty() { @@ -646,7 +646,7 @@ public function getItemsQty() } /** - * {@inheritdoc} + * @inheritdoc */ public function setItemsQty($itemsQty) { @@ -654,7 +654,7 @@ public function setItemsQty($itemsQty) } /** - * {@inheritdoc} + * @inheritdoc */ public function getOrigOrderId() { @@ -662,7 +662,7 @@ public function getOrigOrderId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrigOrderId($origOrderId) { @@ -670,7 +670,7 @@ public function setOrigOrderId($origOrderId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getReservedOrderId() { @@ -678,7 +678,7 @@ public function getReservedOrderId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setReservedOrderId($reservedOrderId) { @@ -686,7 +686,7 @@ public function setReservedOrderId($reservedOrderId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomerIsGuest() { @@ -694,7 +694,7 @@ public function getCustomerIsGuest() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerIsGuest($customerIsGuest) { @@ -702,7 +702,7 @@ public function setCustomerIsGuest($customerIsGuest) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomerNote() { @@ -710,7 +710,7 @@ public function getCustomerNote() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerNote($customerNote) { @@ -718,7 +718,7 @@ public function setCustomerNote($customerNote) } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomerNoteNotify() { @@ -726,7 +726,7 @@ public function getCustomerNoteNotify() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerNoteNotify($customerNoteNotify) { @@ -736,7 +736,7 @@ public function setCustomerNoteNotify($customerNoteNotify) //@codeCoverageIgnoreEnd /** - * {@inheritdoc} + * @inheritdoc */ public function getStoreId() { @@ -747,7 +747,7 @@ public function getStoreId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreId($storeId) { @@ -1078,7 +1078,7 @@ public function getCustomerGroupId() } /** - * {@inheritdoc} + * @inheritdoc */ public function getCustomerTaxClassId() { @@ -1097,7 +1097,7 @@ public function getCustomerTaxClassId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerTaxClassId($customerTaxClassId) { @@ -1399,7 +1399,7 @@ public function getAllVisibleItems() { $items = []; foreach ($this->getItemsCollection() as $item) { - if (!$item->isDeleted() && !$item->getParentItemId()) { + if (!$item->isDeleted() && !$item->getParentItemId() && !$item->getParentItem()) { $items[] = $item; } } @@ -1608,7 +1608,7 @@ public function addProduct( * Error message */ if (is_string($cartCandidates) || $cartCandidates instanceof \Magento\Framework\Phrase) { - return strval($cartCandidates); + return (string)$cartCandidates; } /** @@ -2341,6 +2341,7 @@ public function merge(Quote $quote) foreach ($this->getAllItems() as $quoteItem) { if ($quoteItem->compare($item)) { $quoteItem->setQty($quoteItem->getQty() + $item->getQty()); + $this->itemProcessor->merge($item, $quoteItem); $found = true; break; } diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index 87c5feaba8f2e..3eb5d68885035 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -872,13 +872,7 @@ public function getGroupedAllShippingRates() */ protected function _sortRates($firstItem, $secondItem) { - if ((int)$firstItem[0]->carrier_sort_order < (int)$secondItem[0]->carrier_sort_order) { - return -1; - } elseif ((int)$firstItem[0]->carrier_sort_order > (int)$secondItem[0]->carrier_sort_order) { - return 1; - } else { - return 0; - } + return (int) $firstItem[0]->carrier_sort_order <=> (int) $secondItem[0]->carrier_sort_order; } /** diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php index 6e6aa27ec5f30..84f1fc1c35adf 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php @@ -6,6 +6,7 @@ namespace Magento\Quote\Model\Quote\Address\Total; use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Model\Quote\Address\FreeShippingInterface; class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal @@ -40,7 +41,6 @@ public function __construct( * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment * @param \Magento\Quote\Model\Quote\Address\Total $total * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -54,13 +54,6 @@ public function collect( $address = $shippingAssignment->getShipping()->getAddress(); $method = $shippingAssignment->getShipping()->getMethod(); - $address->setWeight(0); - $address->setFreeMethodWeight(0); - - $addressWeight = $address->getWeight(); - $freeMethodWeight = $address->getFreeMethodWeight(); - $addressFreeShipping = $address->getFreeShipping(); - $total->setTotalAmount($this->getCode(), 0); $total->setBaseTotalAmount($this->getCode(), 0); @@ -68,97 +61,20 @@ public function collect( return $this; } - $addressQty = 0; - foreach ($shippingAssignment->getItems() as $item) { - /** - * Skip if this item is virtual - */ - if ($item->getProduct()->isVirtual()) { - continue; - } - - /** - * Children weight we calculate for parent - */ - if ($item->getParentItem()) { - continue; - } - - if ($item->getHasChildren() && $item->isShipSeparately()) { - foreach ($item->getChildren() as $child) { - if ($child->getProduct()->isVirtual()) { - continue; - } - $addressQty += $child->getTotalQty(); - - if (!$item->getProduct()->getWeightType()) { - $itemWeight = $child->getWeight(); - $itemQty = $child->getTotalQty(); - $rowWeight = $itemWeight * $itemQty; - $addressWeight += $rowWeight; - if ($addressFreeShipping || $child->getFreeShipping() === true) { - $rowWeight = 0; - } elseif (is_numeric($child->getFreeShipping())) { - $freeQty = $child->getFreeShipping(); - if ($itemQty > $freeQty) { - $rowWeight = $itemWeight * ($itemQty - $freeQty); - } else { - $rowWeight = 0; - } - } - $freeMethodWeight += $rowWeight; - $item->setRowWeight($rowWeight); - } - } - if ($item->getProduct()->getWeightType()) { - $itemWeight = $item->getWeight(); - $rowWeight = $itemWeight * $item->getQty(); - $addressWeight += $rowWeight; - if ($addressFreeShipping || $item->getFreeShipping() === true) { - $rowWeight = 0; - } elseif (is_numeric($item->getFreeShipping())) { - $freeQty = $item->getFreeShipping(); - if ($item->getQty() > $freeQty) { - $rowWeight = $itemWeight * ($item->getQty() - $freeQty); - } else { - $rowWeight = 0; - } - } - $freeMethodWeight += $rowWeight; - $item->setRowWeight($rowWeight); - } - } else { - if (!$item->getProduct()->isVirtual()) { - $addressQty += $item->getQty(); - } - $itemWeight = $item->getWeight(); - $rowWeight = $itemWeight * $item->getQty(); - $addressWeight += $rowWeight; - if ($addressFreeShipping || $item->getFreeShipping() === true) { - $rowWeight = 0; - } elseif (is_numeric($item->getFreeShipping())) { - $freeQty = $item->getFreeShipping(); - if ($item->getQty() > $freeQty) { - $rowWeight = $itemWeight * ($item->getQty() - $freeQty); - } else { - $rowWeight = 0; - } - } - $freeMethodWeight += $rowWeight; - $item->setRowWeight($rowWeight); - } + $data = $this->getAssignmentWeightData($address, $shippingAssignment->getItems()); + $address->setItemQty($data['addressQty']); + $address->setWeight($data['addressWeight']); + $address->setFreeMethodWeight($data['freeMethodWeight']); + $addressFreeShipping = (bool)$address->getFreeShipping(); + $isFreeShipping = $this->freeShipping->isFreeShipping($quote, $shippingAssignment->getItems()); + $address->setFreeShipping($isFreeShipping); + if (!$addressFreeShipping && $isFreeShipping) { + $data = $this->getAssignmentWeightData($address, $shippingAssignment->getItems()); + $address->setItemQty($data['addressQty']); + $address->setWeight($data['addressWeight']); + $address->setFreeMethodWeight($data['freeMethodWeight']); } - if (isset($addressQty)) { - $address->setItemQty($addressQty); - } - - $address->setWeight($addressWeight); - $address->setFreeMethodWeight($freeMethodWeight); - $address->setFreeShipping( - $this->freeShipping->isFreeShipping($quote, $shippingAssignment->getItems()) - ); - $address->collectShippingRates(); if ($method) { @@ -215,4 +131,122 @@ public function getLabel() { return __('Shipping'); } + + /** + * Gets shipping assignments data like items weight, address weight, items quantity. + * + * @param AddressInterface $address + * @param array $items + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function getAssignmentWeightData(AddressInterface $address, array $items): array + { + $address->setWeight(0); + $address->setFreeMethodWeight(0); + $addressWeight = $address->getWeight(); + $freeMethodWeight = $address->getFreeMethodWeight(); + $addressFreeShipping = (bool)$address->getFreeShipping(); + $addressQty = 0; + foreach ($items as $item) { + /** + * Skip if this item is virtual + */ + if ($item->getProduct()->isVirtual()) { + continue; + } + + /** + * Children weight we calculate for parent + */ + if ($item->getParentItem()) { + continue; + } + + $itemQty = (float)$item->getQty(); + $itemWeight = (float)$item->getWeight(); + + if ($item->getHasChildren() && $item->isShipSeparately()) { + foreach ($item->getChildren() as $child) { + if ($child->getProduct()->isVirtual()) { + continue; + } + $addressQty += $child->getTotalQty(); + + if (!$item->getProduct()->getWeightType()) { + $itemWeight = (float)$child->getWeight(); + $itemQty = (float)$child->getTotalQty(); + $addressWeight += ($itemWeight * $itemQty); + $rowWeight = $this->getItemRowWeight( + $addressFreeShipping, + $itemWeight, + $itemQty, + $child->getFreeShipping() + ); + $freeMethodWeight += $rowWeight; + $item->setRowWeight($rowWeight); + } + } + if ($item->getProduct()->getWeightType()) { + $addressWeight += ($itemWeight * $itemQty); + $rowWeight = $this->getItemRowWeight( + $addressFreeShipping, + $itemWeight, + $itemQty, + $item->getFreeShipping() + ); + $freeMethodWeight += $rowWeight; + $item->setRowWeight($rowWeight); + } + } else { + if (!$item->getProduct()->isVirtual()) { + $addressQty += $itemQty; + } + $addressWeight += ($itemWeight * $itemQty); + $rowWeight = $this->getItemRowWeight( + $addressFreeShipping, + $itemWeight, + $itemQty, + $item->getFreeShipping() + ); + $freeMethodWeight += $rowWeight; + $item->setRowWeight($rowWeight); + } + } + + return [ + 'addressQty' => $addressQty, + 'addressWeight' => $addressWeight, + 'freeMethodWeight' => $freeMethodWeight + ]; + } + + /** + * Calculates item row weight. + * + * @param bool $addressFreeShipping + * @param float $itemWeight + * @param float $itemQty + * @param $freeShipping + * @return float + */ + private function getItemRowWeight( + bool $addressFreeShipping, + float $itemWeight, + float $itemQty, + $freeShipping + ): float { + $rowWeight = $itemWeight * $itemQty; + if ($addressFreeShipping || $freeShipping === true) { + $rowWeight = 0; + } elseif (is_numeric($freeShipping)) { + $freeQty = $freeShipping; + if ($itemQty > $freeQty) { + $rowWeight = $itemWeight * ($itemQty - $freeQty); + } else { + $rowWeight = 0; + } + } + return (float)$rowWeight; + } } diff --git a/app/code/Magento/Quote/Model/Quote/Item/Processor.php b/app/code/Magento/Quote/Model/Quote/Item/Processor.php index f34591cfad143..2577008ecbae3 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Processor.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Processor.php @@ -5,7 +5,7 @@ */ namespace Magento\Quote\Model\Quote\Item; -use \Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product; use Magento\Quote\Model\Quote\ItemFactory; use Magento\Quote\Model\Quote\Item; use Magento\Store\Model\StoreManagerInterface; @@ -53,12 +53,12 @@ public function __construct( /** * Initialize quote item object * - * @param \Magento\Framework\DataObject $request + * @param DataObject $request * @param Product $product * - * @return \Magento\Quote\Model\Quote\Item + * @return Item */ - public function init(Product $product, $request) + public function init(Product $product, DataObject $request): Item { $item = $this->quoteItemFactory->create(); @@ -82,11 +82,11 @@ public function init(Product $product, $request) * Set qty and custom price for quote item * * @param Item $item - * @param \Magento\Framework\DataObject $request + * @param DataObject $request * @param Product $candidate * @return void */ - public function prepare(Item $item, DataObject $request, Product $candidate) + public function prepare(Item $item, DataObject $request, Product $candidate): void { /** * We specify qty after we know about parent (for stock) @@ -103,13 +103,27 @@ public function prepare(Item $item, DataObject $request, Product $candidate) } } + /** + * Merge two quote items. + * + * @param Item $source + * @param Item $target + * @return Item + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function merge(Item $source, Item $target): Item + { + return $target; + } + /** * Set store_id value to quote item * * @param Item $item * @return void */ - protected function setItemStoreId(Item $item) + protected function setItemStoreId(Item $item): void { if ($this->appState->getAreaCode() === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) { $storeId = $this->storeManager->getStore($this->storeManager->getStore()->getId()) diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 13021ee412754..a718d9df9d923 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -564,6 +564,10 @@ protected function _prepareCustomerQuote($quote) //Make provided address as default shipping address $shippingAddress->setIsDefaultShipping(true); $hasDefaultShipping = true; + if (!$hasDefaultBilling && !$billing->getSaveInAddressBook()) { + $shippingAddress->setIsDefaultBilling(true); + $hasDefaultBilling = true; + } } //save here new customer address $shippingAddress->setCustomerId($quote->getCustomerId()); diff --git a/app/code/Magento/Quote/Model/QuoteRepository.php b/app/code/Magento/Quote/Model/QuoteRepository.php index d3967794b300a..01c21bbbe50a7 100644 --- a/app/code/Magento/Quote/Model/QuoteRepository.php +++ b/app/code/Magento/Quote/Model/QuoteRepository.php @@ -212,7 +212,7 @@ protected function loadQuote($loadMethod, $loadField, $identifier, array $shared if ($sharedStoreIds) { $quote->setSharedStoreIds($sharedStoreIds); } - $quote->$loadMethod($identifier)->setStoreId($this->storeManager->getStore()->getId()); + $quote->setStoreId($this->storeManager->getStore()->getId())->$loadMethod($identifier); if (!$quote->getId()) { throw NoSuchEntityException::singleField($loadField, $identifier); } diff --git a/app/code/Magento/Quote/Model/QuoteValidator.php b/app/code/Magento/Quote/Model/QuoteValidator.php index 1f6deca4c1d74..04d6d4ecba160 100644 --- a/app/code/Magento/Quote/Model/QuoteValidator.php +++ b/app/code/Magento/Quote/Model/QuoteValidator.php @@ -11,6 +11,7 @@ use Magento\Directory\Model\AllowedCountries; use Magento\Framework\App\ObjectManager; use Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage as OrderAmountValidationMessage; +use Magento\Quote\Model\ValidationRules\QuoteValidationRuleInterface; /** * @api @@ -33,20 +34,29 @@ class QuoteValidator */ private $minimumAmountMessage; + /** + * @var QuoteValidationRuleInterface + */ + private $quoteValidationRule; + /** * QuoteValidator constructor. * * @param AllowedCountries|null $allowedCountryReader * @param OrderAmountValidationMessage|null $minimumAmountMessage + * @param QuoteValidationRuleInterface|null $quoteValidationRule */ public function __construct( AllowedCountries $allowedCountryReader = null, - OrderAmountValidationMessage $minimumAmountMessage = null + OrderAmountValidationMessage $minimumAmountMessage = null, + QuoteValidationRuleInterface $quoteValidationRule = null ) { $this->allowedCountryReader = $allowedCountryReader ?: ObjectManager::getInstance() ->get(AllowedCountries::class); $this->minimumAmountMessage = $minimumAmountMessage ?: ObjectManager::getInstance() ->get(OrderAmountValidationMessage::class); + $this->quoteValidationRule = $quoteValidationRule ?: ObjectManager::getInstance() + ->get(QuoteValidationRuleInterface::class); } /** @@ -74,50 +84,20 @@ public function validateQuoteAmount(QuoteEntity $quote, $amount) */ public function validateBeforeSubmit(QuoteEntity $quote) { - if (!$quote->isVirtual()) { - if ($quote->getShippingAddress()->validate() !== true) { - throw new \Magento\Framework\Exception\LocalizedException( - __( - 'Please check the shipping address information. %1', - implode(' ', $quote->getShippingAddress()->validate()) - ) - ); + foreach ($this->quoteValidationRule->validate($quote) as $validationResult) { + if ($validationResult->isValid()) { + continue; } - // Checks if country id present in the allowed countries list. - if (!in_array( - $quote->getShippingAddress()->getCountryId(), - $this->allowedCountryReader->getAllowedCountries() - )) { - throw new \Magento\Framework\Exception\LocalizedException( - __("Some addresses can't be used due to the configurations for specific countries.") - ); + $messages = $validationResult->getErrors(); + $defaultMessage = array_shift($messages); + if ($defaultMessage && !empty($messages)) { + $defaultMessage .= ' %1'; } - - $method = $quote->getShippingAddress()->getShippingMethod(); - $rate = $quote->getShippingAddress()->getShippingRateByCode($method); - if (!$method || !$rate) { - throw new \Magento\Framework\Exception\LocalizedException( - __('The shipping method is missing. Select the shipping method and try again.') - ); + if ($defaultMessage) { + throw new LocalizedException(__($defaultMessage, implode(' ', $messages))); } } - if ($quote->getBillingAddress()->validate() !== true) { - throw new \Magento\Framework\Exception\LocalizedException( - __( - 'Please check the billing address information. %1', - implode(' ', $quote->getBillingAddress()->validate()) - ) - ); - } - if (!$quote->getPayment()->getMethod()) { - throw new \Magento\Framework\Exception\LocalizedException( - __('Enter a valid payment method and try again. ') - ); - } - if (!$quote->validateMinimumAmount($quote->getIsMultiShipping())) { - throw new LocalizedException($this->minimumAmountMessage->getMessage()); - } return $this; } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php index 0487d7e46eb26..c7525754dffd9 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php @@ -45,6 +45,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionContro */ protected $_quoteConfig; + /** + * @var \Magento\Store\Model\StoreManagerInterface|null + */ + private $storeManager; + /** * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger @@ -56,6 +61,7 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionContro * @param \Magento\Quote\Model\Quote\Config $quoteConfig * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource + * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -68,7 +74,8 @@ public function __construct( \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, \Magento\Quote\Model\Quote\Config $quoteConfig, \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null + \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null, + \Magento\Store\Model\StoreManagerInterface $storeManager = null ) { parent::__construct( $entityFactory, @@ -82,6 +89,10 @@ public function __construct( $this->_itemOptionCollectionFactory = $itemOptionCollectionFactory; $this->_productCollectionFactory = $productCollectionFactory; $this->_quoteConfig = $quoteConfig; + + // Backward compatibility constructor parameters + $this->storeManager = $storeManager ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Store\Model\StoreManagerInterface::class); } /** @@ -101,7 +112,10 @@ protected function _construct() */ public function getStoreId() { - return (int)$this->_productCollectionFactory->create()->getStoreId(); + // Fallback to current storeId if no quote is provided + // (see https://github.com/magento/magento2/commit/9d3be732a88884a66d667b443b3dc1655ddd0721) + return $this->_quote === null ? + (int) $this->storeManager->getStore()->getId() : (int) $this->_quote->getStoreId(); } /** @@ -309,6 +323,10 @@ private function addTierPriceData(ProductCollection $productCollection) */ private function removeItemsWithAbsentProducts() { + if (count($this->_productIds) === 0) { + return; + } + $productCollection = $this->_productCollectionFactory->create()->addIdFilter($this->_productIds); $existingProductsIds = $productCollection->getAllIds(); $absentProductsIds = array_diff($this->_productIds, $existingProductsIds); diff --git a/app/code/Magento/Quote/Model/ShippingMethodManagement.php b/app/code/Magento/Quote/Model/ShippingMethodManagement.php index ade2649d0b1b0..f62866539c6cc 100644 --- a/app/code/Magento/Quote/Model/ShippingMethodManagement.php +++ b/app/code/Magento/Quote/Model/ShippingMethodManagement.php @@ -16,6 +16,7 @@ use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Api\Data\EstimateAddressInterface; use Magento\Quote\Api\ShipmentEstimationInterface; +use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource; /** * Shipping method read service @@ -63,6 +64,11 @@ class ShippingMethodManagement implements */ private $addressFactory; + /** + * @var QuoteAddressResource + */ + private $quoteAddressResource; + /** * Constructor * @@ -71,13 +77,15 @@ class ShippingMethodManagement implements * @param \Magento\Customer\Api\AddressRepositoryInterface $addressRepository * @param Quote\TotalsCollector $totalsCollector * @param AddressInterfaceFactory|null $addressFactory + * @param QuoteAddressResource|null $quoteAddressResource */ public function __construct( \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, Cart\ShippingMethodConverter $converter, \Magento\Customer\Api\AddressRepositoryInterface $addressRepository, \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector, - AddressInterfaceFactory $addressFactory = null + AddressInterfaceFactory $addressFactory = null, + QuoteAddressResource $quoteAddressResource = null ) { $this->quoteRepository = $quoteRepository; $this->converter = $converter; @@ -85,6 +93,8 @@ public function __construct( $this->totalsCollector = $totalsCollector; $this->addressFactory = $addressFactory ?: ObjectManager::getInstance() ->get(AddressInterfaceFactory::class); + $this->quoteAddressResource = $quoteAddressResource ?: ObjectManager::getInstance() + ->get(QuoteAddressResource::class); } /** @@ -171,9 +181,9 @@ public function set($cartId, $carrierCode, $methodCode) * @param string $methodCode The shipping method code. * @return void * @throws InputException The shipping method is not valid for an empty cart. - * @throws CouldNotSaveException The shipping method could not be saved. * @throws NoSuchEntityException CThe Cart includes virtual product(s) only, so a shipping address is not used. - * @throws StateException The billing or shipping address is missing. Set the address and try again. + * @throws StateException The billing or shipping address is not set. + * @throws \Exception */ public function apply($cartId, $carrierCode, $methodCode) { @@ -191,6 +201,8 @@ public function apply($cartId, $carrierCode, $methodCode) } $shippingAddress = $quote->getShippingAddress(); if (!$shippingAddress->getCountryId()) { + // Remove empty quote address + $this->quoteAddressResource->delete($shippingAddress); throw new StateException(__('The shipping address is missing. Set the address and try again.')); } $shippingAddress->setShippingMethod($carrierCode . '_' . $methodCode); diff --git a/app/code/Magento/Quote/Model/ValidationRules/AllowedCountryValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/AllowedCountryValidationRule.php new file mode 100644 index 0000000000000..840e94e3ece1f --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/AllowedCountryValidationRule.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Directory\Model\AllowedCountries; +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class AllowedCountryValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var AllowedCountries + */ + private $allowedCountryReader; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param AllowedCountries $allowedCountryReader + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + AllowedCountries $allowedCountryReader, + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->allowedCountryReader = $allowedCountryReader; + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + + if (!$quote->isVirtual()) { + $validationResult = + in_array( + $quote->getShippingAddress()->getCountryId(), + $this->allowedCountryReader->getAllowedCountries() + ); + if (!$validationResult) { + $validationErrors = [__($this->generalMessage)]; + } + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php new file mode 100644 index 0000000000000..fbacbf1c8d30c --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/BillingAddressValidationRule.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class BillingAddressValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + $validationResult = $quote->getBillingAddress()->validate(); + if ($validationResult !== true) { + $validationErrors = [__($this->generalMessage)]; + } + if (is_array($validationResult)) { + $validationErrors = array_merge($validationErrors, $validationResult); + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php new file mode 100644 index 0000000000000..34e953be43c74 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/MinimumAmountValidationRule.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage; + +/** + * @inheritdoc + */ +class MinimumAmountValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var ValidationMessage + */ + private $amountValidationMessage; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param ValidationMessage $amountValidationMessage + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + ValidationMessage $amountValidationMessage, + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->amountValidationMessage = $amountValidationMessage; + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + * @throws \Zend_Currency_Exception + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + $validationResult = $quote->validateMinimumAmount($quote->getIsMultiShipping()); + if (!$validationResult) { + if (!$this->generalMessage) { + $this->generalMessage = $this->amountValidationMessage->getMessage(); + } + $validationErrors = [__($this->generalMessage)]; + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/PaymentMethodValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/PaymentMethodValidationRule.php new file mode 100644 index 0000000000000..bf2b813541fb0 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/PaymentMethodValidationRule.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class PaymentMethodValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + $validationResult = $quote->getPayment()->getMethod(); + if (!$validationResult) { + $validationErrors = [__($this->generalMessage)]; + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationComposite.php b/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationComposite.php new file mode 100644 index 0000000000000..6a75be3acce89 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationComposite.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class QuoteValidationComposite implements QuoteValidationRuleInterface +{ + /** + * @var QuoteValidationRuleInterface[] + */ + private $validationRules = []; + + /** + * @param QuoteValidationRuleInterface[] $validationRules + * @throws \InvalidArgumentException + */ + public function __construct(array $validationRules) + { + foreach ($validationRules as $validationRule) { + if (!($validationRule instanceof QuoteValidationRuleInterface)) { + throw new \InvalidArgumentException( + sprintf( + 'Instance of the ValidationRuleInterface is expected, got %s instead.', + get_class($validationRule) + ) + ); + } + } + $this->validationRules = $validationRules; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $aggregateResult = []; + + foreach ($this->validationRules as $validationRule) { + $ruleValidationResult = $validationRule->validate($quote); + foreach ($ruleValidationResult as $item) { + if (!$item->isValid()) { + array_push($aggregateResult, $item); + } + } + } + + return $aggregateResult; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationRuleInterface.php b/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationRuleInterface.php new file mode 100644 index 0000000000000..1a777e3f1a5fe --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/QuoteValidationRuleInterface.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResult; +use Magento\Quote\Model\Quote; + +/** + * Provides validation of Quote model. + */ +interface QuoteValidationRuleInterface +{ + /** + * Validate Quote model. + * + * @param Quote $quote + * @return ValidationResult[] + */ + public function validate(Quote $quote): array; +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php new file mode 100644 index 0000000000000..f5eebe241acc9 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/ShippingAddressValidationRule.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class ShippingAddressValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + + if (!$quote->isVirtual()) { + $validationResult = $quote->getShippingAddress()->validate(); + if ($validationResult !== true) { + $validationErrors = [__($this->generalMessage)]; + } + if (is_array($validationResult)) { + $validationErrors = array_merge($validationErrors, $validationResult); + } + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/ValidationRules/ShippingMethodValidationRule.php b/app/code/Magento/Quote/Model/ValidationRules/ShippingMethodValidationRule.php new file mode 100644 index 0000000000000..6df7f663b0630 --- /dev/null +++ b/app/code/Magento/Quote/Model/ValidationRules/ShippingMethodValidationRule.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\ValidationRules; + +use Magento\Framework\Validation\ValidationResultFactory; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class ShippingMethodValidationRule implements QuoteValidationRuleInterface +{ + /** + * @var string + */ + private $generalMessage; + + /** + * @var ValidationResultFactory + */ + private $validationResultFactory; + + /** + * @param ValidationResultFactory $validationResultFactory + * @param string $generalMessage + */ + public function __construct( + ValidationResultFactory $validationResultFactory, + string $generalMessage = '' + ) { + $this->validationResultFactory = $validationResultFactory; + $this->generalMessage = $generalMessage; + } + + /** + * @inheritdoc + */ + public function validate(Quote $quote): array + { + $validationErrors = []; + + if (!$quote->isVirtual()) { + $shippingMethod = $quote->getShippingAddress()->getShippingMethod(); + $shippingRate = $quote->getShippingAddress()->getShippingRateByCode($shippingMethod); + $validationResult = $shippingMethod && $shippingRate; + if (!$validationResult) { + $validationErrors = [__($this->generalMessage)]; + } + } + + return [$this->validationResultFactory->create(['errors' => $validationErrors])]; + } +} diff --git a/app/code/Magento/Quote/Model/Webapi/ParamOverriderCartId.php b/app/code/Magento/Quote/Model/Webapi/ParamOverriderCartId.php index a13a2dc590def..ae6c82629c437 100644 --- a/app/code/Magento/Quote/Model/Webapi/ParamOverriderCartId.php +++ b/app/code/Magento/Quote/Model/Webapi/ParamOverriderCartId.php @@ -56,7 +56,7 @@ public function getOverriddenValue() } } } catch (NoSuchEntityException $e) { - /* do nothing and just return null */ + throw new NoSuchEntityException(__('Current customer does not have an active cart.')); } return null; } diff --git a/app/code/Magento/Quote/Plugin/UpdateQuoteStore.php b/app/code/Magento/Quote/Plugin/UpdateQuoteStore.php new file mode 100644 index 0000000000000..de2fc76b9179f --- /dev/null +++ b/app/code/Magento/Quote/Plugin/UpdateQuoteStore.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Plugin; + +use Magento\Checkout\Model\Session; +use Magento\Quote\Model\QuoteRepository; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\StoreCookieManagerInterface; + +/** + * Updates quote store id. + */ +class UpdateQuoteStore +{ + /** + * @var QuoteRepository + */ + private $quoteRepository; + + /** + * @var Session + */ + private $checkoutSession; + + /** + * @param QuoteRepository $quoteRepository + * @param Session $checkoutSession + */ + public function __construct( + QuoteRepository $quoteRepository, + Session $checkoutSession + ) { + $this->quoteRepository = $quoteRepository; + $this->checkoutSession = $checkoutSession; + } + + /** + * Update store id in active quote after store view switching. + * + * @param StoreCookieManagerInterface $subject + * @param null $result + * @param StoreInterface $store + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSetStoreCookie( + StoreCookieManagerInterface $subject, + $result, + StoreInterface $store + ) { + $storeCodeFromCookie = $subject->getStoreCodeFromCookie(); + if (null === $storeCodeFromCookie) { + return; + } + + $quote = $this->checkoutSession->getQuote(); + if ($quote->getIsActive() && $store->getCode() != $storeCodeFromCookie) { + $quote->setStoreId( + $store->getId() + ); + $this->quoteRepository->save($quote); + } + } +} diff --git a/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml b/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml index d4032b5f1ac56..4cc2c4f392419 100644 --- a/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml +++ b/app/code/Magento/Quote/Test/Mftf/Data/CartItemData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SimpleCartItem" type="CartItem"> <data key="qty">1</data> <var key="quote_id" entityKey="return" entityType="GuestCart"/> diff --git a/app/code/Magento/Quote/Test/Mftf/Data/GuestCartData.xml b/app/code/Magento/Quote/Test/Mftf/Data/GuestCartData.xml index 062c4ebbad333..1d63a8a0d9f87 100644 --- a/app/code/Magento/Quote/Test/Mftf/Data/GuestCartData.xml +++ b/app/code/Magento/Quote/Test/Mftf/Data/GuestCartData.xml @@ -8,7 +8,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="GuestCart" type="GuestCart"> </entity> diff --git a/app/code/Magento/Quote/Test/Mftf/Metadata/billing_address-meta.xml b/app/code/Magento/Quote/Test/Mftf/Metadata/billing_address-meta.xml index a21c4a30e8077..d4a4c4345d738 100644 --- a/app/code/Magento/Quote/Test/Mftf/Metadata/billing_address-meta.xml +++ b/app/code/Magento/Quote/Test/Mftf/Metadata/billing_address-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateBillingAddress" dataType="billing_address" type="create"> <field key="city">string</field> <field key="region">string</field> diff --git a/app/code/Magento/Quote/Test/Mftf/Metadata/guest_cart-meta.xml b/app/code/Magento/Quote/Test/Mftf/Metadata/guest_cart-meta.xml index e08a30407889f..27c7af95d0f2c 100644 --- a/app/code/Magento/Quote/Test/Mftf/Metadata/guest_cart-meta.xml +++ b/app/code/Magento/Quote/Test/Mftf/Metadata/guest_cart-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateGuestCart" dataType="GuestCart" type="create" auth="anonymous" url="/V1/guest-carts" method="POST"> <contentType>application/json</contentType> </operation> diff --git a/app/code/Magento/Quote/Test/Mftf/Metadata/shipping_address-meta.xml b/app/code/Magento/Quote/Test/Mftf/Metadata/shipping_address-meta.xml index 537f6c2a2f87d..803681252a9e9 100644 --- a/app/code/Magento/Quote/Test/Mftf/Metadata/shipping_address-meta.xml +++ b/app/code/Magento/Quote/Test/Mftf/Metadata/shipping_address-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateShippingAddress" dataType="shipping_address" type="create"> <field key="city">string</field> <field key="region">string</field> diff --git a/app/code/Magento/Quote/Test/Mftf/composer.json b/app/code/Magento/Quote/Test/Mftf/composer.json deleted file mode 100644 index 62932a84f3059..0000000000000 --- a/app/code/Magento/Quote/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-quote", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-sales-sequence": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Quote/Test/Unit/Model/CouponManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/CouponManagementTest.php index e6ba50e35b4c3..91211904c11eb 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/CouponManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/CouponManagementTest.php @@ -47,6 +47,7 @@ protected function setUp() 'save', 'getShippingAddress', 'getCouponCode', + 'getStoreId', '__wakeup' ]); $this->quoteAddressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, [ @@ -98,6 +99,9 @@ public function testSetWhenCouldNotApplyCoupon() $cartId = 33; $couponCode = '153a-ABC'; + $this->storeMock->expects($this->any())->method('getId')->will($this->returnValue(1)); + $this->quoteMock->expects($this->once())->method('getStoreId')->willReturn($this->returnValue(1)); + $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once())->method('getItemsCount')->will($this->returnValue(12)); @@ -125,6 +129,9 @@ public function testSetWhenCouponCodeIsInvalid() $cartId = 33; $couponCode = '153a-ABC'; + $this->storeMock->expects($this->any())->method('getId')->will($this->returnValue(1)); + $this->quoteMock->expects($this->once())->method('getStoreId')->willReturn($this->returnValue(1)); + $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once())->method('getItemsCount')->will($this->returnValue(12)); @@ -144,6 +151,9 @@ public function testSet() $cartId = 33; $couponCode = '153a-ABC'; + $this->storeMock->expects($this->any())->method('getId')->will($this->returnValue(1)); + $this->quoteMock->expects($this->once())->method('getStoreId')->willReturn($this->returnValue(1)); + $this->quoteRepositoryMock->expects($this->once()) ->method('getActive')->with($cartId)->will($this->returnValue($this->quoteMock)); $this->quoteMock->expects($this->once())->method('getItemsCount')->will($this->returnValue(12)); diff --git a/app/code/Magento/Quote/Test/Unit/Model/GuestCart/GuestCartManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/GuestCart/GuestCartManagementTest.php index ee0ffd3bcc666..73ed2e65b41a9 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/GuestCart/GuestCartManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/GuestCart/GuestCartManagementTest.php @@ -94,7 +94,7 @@ public function testCreateEmptyCart() $cartId = 1; $this->quoteIdMaskMock->expects($this->once())->method('setQuoteId')->with($cartId)->willReturnSelf(); $this->quoteIdMaskMock->expects($this->once())->method('save')->willReturnSelf(); - $this->quoteIdMaskMock->expects($this->once())->method('getMaskedId')->willreturn($maskedCartId); + $this->quoteIdMaskMock->expects($this->once())->method('getMaskedId')->willReturn($maskedCartId); $this->quoteIdMaskFactoryMock->expects($this->once())->method('create')->willReturn($this->quoteIdMaskMock); $this->quoteManagementMock->expects($this->once())->method('createEmptyCart')->willReturn($cartId); diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Address/Total/ShippingTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Address/Total/ShippingTest.php index 6c572b1e86d08..999595357c05a 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Address/Total/ShippingTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Address/Total/ShippingTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Quote\Test\Unit\Model\Quote\Address\Total; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ShippingTest extends \PHPUnit\Framework\TestCase { /** @@ -42,6 +45,9 @@ class ShippingTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $priceCurrency; + /** + * @inheritdoc + */ protected function setUp() { $this->freeShipping = $this->getMockForAbstractClass( @@ -123,7 +129,10 @@ protected function setUp() $this->store = $this->createMock(\Magento\Store\Model\Store::class); } - public function testFetch() + /** + * @return void + */ + public function testFetch(): void { $shippingAmount = 100; $shippingDescription = 100; @@ -144,7 +153,10 @@ public function testFetch() $this->assertEquals($expectedResult, $this->shippingModel->fetch($quoteMock, $totalMock)); } - public function testCollect() + /** + * @return void + */ + public function testCollect(): void { $this->shippingAssignment->expects($this->exactly(3)) ->method('getShipping') @@ -158,12 +170,10 @@ public function testCollect() $this->shippingAssignment->expects($this->atLeastOnce()) ->method('getItems') ->willReturn([$this->cartItem]); - $this->freeShipping->expects($this->once()) - ->method('isFreeShipping') + $this->freeShipping->method('isFreeShipping') ->with($this->quote, [$this->cartItem]) ->willReturn(true); - $this->address->expects($this->once()) - ->method('setFreeShipping') + $this->address->method('setFreeShipping') ->with(true); $this->total->expects($this->atLeastOnce()) ->method('setTotalAmount'); @@ -175,24 +185,19 @@ public function testCollect() $this->cartItem->expects($this->atLeastOnce()) ->method('isVirtual') ->willReturn(false); - $this->cartItem->expects($this->once()) - ->method('getParentItem') + $this->cartItem->method('getParentItem') ->willReturn(false); - $this->cartItem->expects($this->once()) - ->method('getHasChildren') + $this->cartItem->method('getHasChildren') ->willReturn(false); - $this->cartItem->expects($this->once()) - ->method('getWeight') + $this->cartItem->method('getWeight') ->willReturn(2); $this->cartItem->expects($this->atLeastOnce()) ->method('getQty') ->willReturn(2); $this->freeShippingAssertions(); - $this->cartItem->expects($this->once()) - ->method('setRowWeight') + $this->cartItem->method('setRowWeight') ->with(0); - $this->address->expects($this->once()) - ->method('setItemQty') + $this->address->method('setItemQty') ->with(2); $this->address->expects($this->atLeastOnce()) ->method('setWeight'); @@ -241,7 +246,10 @@ public function testCollect() $this->shippingModel->collect($this->quote, $this->shippingAssignment, $this->total); } - protected function freeShippingAssertions() + /** + * @return void + */ + protected function freeShippingAssertions(): void { $this->address->expects($this->at(0)) ->method('getFreeShipping') diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteValidatorTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteValidatorTest.php index 6865134a04870..d0a3c9fab5131 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteValidatorTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteValidatorTest.php @@ -88,182 +88,4 @@ public function testCheckQuoteAmountExistingError() $this->quoteValidator->validateQuoteAmount($this->quoteMock, QuoteValidator::MAXIMUM_AVAILABLE_NUMBER + 1) ); } - - public function testCheckQuoteAmountAmountLessThanAvailable() - { - $this->quoteMock->expects($this->once()) - ->method('getHasError') - ->will($this->returnValue(false)); - - $this->quoteMock->expects($this->never()) - ->method('setHasError'); - - $this->quoteMock->expects($this->never()) - ->method('addMessage'); - - $this->assertSame( - $this->quoteValidator, - $this->quoteValidator->validateQuoteAmount($this->quoteMock, QuoteValidator::MAXIMUM_AVAILABLE_NUMBER - 1) - ); - } - - public function testCheckQuoteAmountAmountGreaterThanAvailable() - { - $this->quoteMock->expects($this->once()) - ->method('getHasError') - ->will($this->returnValue(false)); - - $this->quoteMock->expects($this->once()) - ->method('setHasError') - ->with(true); - - $this->quoteMock->expects($this->once()) - ->method('addMessage') - ->with(__('This item price or quantity is not valid for checkout.')); - - $this->assertSame( - $this->quoteValidator, - $this->quoteValidator->validateQuoteAmount($this->quoteMock, QuoteValidator::MAXIMUM_AVAILABLE_NUMBER + 1) - ); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Please check the shipping address information. - */ - public function testValidateBeforeSubmitThrowsExceptionIfShippingAddressIsInvalid() - { - $shippingAddressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class); - $this->quoteMock->expects($this->any())->method('getShippingAddress')->willReturn($shippingAddressMock); - $this->quoteMock->expects($this->any())->method('isVirtual')->willReturn(false); - $shippingAddressMock->expects($this->any())->method('validate')->willReturn(['Invalid Shipping Address']); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage The shipping method is missing. Select the shipping method and try again. - */ - public function testValidateBeforeSubmitThrowsExceptionIfShippingRateIsNotSelected() - { - $shippingMethod = 'checkmo'; - $shippingAddressMock = $this->getMockBuilder(Address::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->allowedCountryReader->method('getAllowedCountries') - ->willReturn(['US' => 'US']); - - $this->quoteMock->expects($this->any())->method('getShippingAddress')->willReturn($shippingAddressMock); - $this->quoteMock->expects($this->any())->method('isVirtual')->willReturn(false); - $shippingAddressMock->expects($this->any())->method('validate')->willReturn(true); - $shippingAddressMock->method('getCountryId') - ->willReturn('US'); - $shippingAddressMock->expects($this->any())->method('getShippingMethod')->willReturn($shippingMethod); - $shippingAddressMock->expects($this->once())->method('getShippingRateByCode')->with($shippingMethod); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Please check the billing address information. - */ - public function testValidateBeforeSubmitThrowsExceptionIfBillingAddressIsNotValid() - { - $billingAddressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class); - $this->quoteMock->expects($this->any())->method('getBillingAddress')->willReturn($billingAddressMock); - $this->quoteMock->expects($this->any())->method('isVirtual')->willReturn(true); - $billingAddressMock->expects($this->any())->method('validate')->willReturn(['Invalid Billing Address']); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Enter a valid payment method and try again. - */ - public function testValidateBeforeSubmitThrowsExceptionIfPaymentMethodIsNotSelected() - { - $paymentMock = $this->createMock(\Magento\Quote\Model\Quote\Payment::class); - $billingAddressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class); - $billingAddressMock->expects($this->any())->method('validate')->willReturn(true); - - $this->quoteMock->expects($this->any())->method('getBillingAddress')->willReturn($billingAddressMock); - $this->quoteMock->expects($this->any())->method('getPayment')->willReturn($paymentMock); - $this->quoteMock->expects($this->any())->method('isVirtual')->willReturn(true); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Minimum Order Amount Exceeded. - */ - public function testValidateBeforeSubmitThrowsExceptionIfMinimumOrderAmount() - { - $paymentMock = $this->createMock(\Magento\Quote\Model\Quote\Payment::class); - $paymentMock->expects($this->once())->method('getMethod')->willReturn('checkmo'); - - $billingAddressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class); - $billingAddressMock->expects($this->any())->method('validate')->willReturn(true); - - $this->quoteMock->expects($this->any())->method('getBillingAddress')->willReturn($billingAddressMock); - $this->quoteMock->expects($this->any())->method('getPayment')->willReturn($paymentMock); - $this->quoteMock->expects($this->any())->method('isVirtual')->willReturn(true); - - $this->quoteMock->expects($this->any())->method('getIsMultiShipping')->willReturn(false); - $this->quoteMock->expects($this->any())->method('validateMinimumAmount')->willReturn(false); - - $this->orderAmountValidationMessage->expects($this->once())->method('getMessage') - ->willReturn(__("Minimum Order Amount Exceeded.")); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } - - /** - * Test case when country id not present in allowed countries list. - * - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Some addresses can't be used due to the configurations for specific countries. - */ - public function testValidateBeforeSubmitThrowsExceptionIfCountrySpecificConfigurations() - { - $this->allowedCountryReader->method('getAllowedCountries') - ->willReturn(['EE' => 'EE']); - - $addressMock = $this->getMockBuilder(Address::class) - ->disableOriginalConstructor() - ->getMock(); - $addressMock->method('validate') - ->willReturn(true); - $addressMock->method('getCountryId') - ->willReturn('EU'); - - $paymentMock = $this->getMockBuilder(Payment::class) - ->setMethods(['getMethod']) - ->disableOriginalConstructor() - ->getMock(); - $paymentMock->method('getMethod') - ->willReturn(true); - - $billingAddressMock = $this->getMockBuilder(Address::class) - ->disableOriginalConstructor() - ->setMethods(['validate']) - ->getMock(); - $billingAddressMock->method('validate') - ->willReturn(true); - - $this->quoteMock->method('getShippingAddress') - ->willReturn($addressMock); - $this->quoteMock->method('isVirtual') - ->willReturn(false); - $this->quoteMock->method('getBillingAddress') - ->willReturn($billingAddressMock); - $this->quoteMock->method('getPayment') - ->willReturn($paymentMock); - - $this->quoteValidator->validateBeforeSubmit($this->quoteMock); - } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php index 198f1c54a42b4..34d7707d31666 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/ShippingMethodManagementTest.php @@ -15,7 +15,9 @@ use Magento\Quote\Model\Quote\Address\Rate; use Magento\Quote\Model\Quote\TotalsCollector; use Magento\Quote\Model\QuoteRepository; +use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource; use Magento\Quote\Model\ShippingMethodManagement; +use Magento\Store\Model\Store; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -83,6 +85,16 @@ class ShippingMethodManagementTest extends \PHPUnit\Framework\TestCase */ private $totalsCollector; + /** + * @var Store|MockObject + */ + private $storeMock; + + /** + * @var QuoteAddressResource|MockObject + */ + private $quoteAddressResource; + protected function setUp() { $this->objectManager = new ObjectManager($this); @@ -98,7 +110,8 @@ protected function setUp() $className = \Magento\Framework\Reflection\DataObjectProcessor::class; $this->dataProcessor = $this->createMock($className); - $this->storeMock = $this->createMock(\Magento\Store\Model\Store::class); + $this->quoteAddressResource = $this->createMock(QuoteAddressResource::class); + $this->storeMock = $this->createMock(Store::class); $this->quote = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods([ @@ -150,6 +163,7 @@ protected function setUp() 'converter' => $this->converter, 'totalsCollector' => $this->totalsCollector, 'addressRepository' => $this->addressRepository, + 'quoteAddressResource' => $this->quoteAddressResource, ] ); @@ -362,6 +376,7 @@ public function testSetMethodWithoutShippingAddress() $this->quote->expects($this->once()) ->method('getShippingAddress')->will($this->returnValue($this->shippingAddress)); $this->shippingAddress->expects($this->once())->method('getCountryId')->will($this->returnValue(null)); + $this->quoteAddressResource->expects($this->once())->method('delete')->with($this->shippingAddress); $this->model->set($cartId, $carrierCode, $methodCode); } @@ -421,6 +436,7 @@ public function testSetMethodWithoutAddress() ->method('getShippingAddress') ->willReturn($this->shippingAddress); $this->shippingAddress->expects($this->once())->method('getCountryId'); + $this->quoteAddressResource->expects($this->once())->method('delete')->with($this->shippingAddress); $this->model->set($cartId, $carrierCode, $methodCode); } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Webapi/ParamOverriderCartIdTest.php b/app/code/Magento/Quote/Test/Unit/Model/Webapi/ParamOverriderCartIdTest.php index bc47a04a226e1..b533463d689a8 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Webapi/ParamOverriderCartIdTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Webapi/ParamOverriderCartIdTest.php @@ -67,6 +67,9 @@ public function testGetOverriddenValueIsCustomerAndCartExists() $this->assertSame($retValue, $this->model->getOverriddenValue()); } + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ public function testGetOverriddenValueIsCustomerAndCartDoesNotExist() { $customerId = 1; @@ -83,7 +86,7 @@ public function testGetOverriddenValueIsCustomerAndCartDoesNotExist() ->with($customerId) ->will($this->throwException(new NoSuchEntityException())); - $this->assertNull($this->model->getOverriddenValue()); + $this->model->getOverriddenValue(); } public function testGetOverriddenValueIsCustomerAndCartIsNull() diff --git a/app/code/Magento/Quote/etc/adminhtml/di.xml b/app/code/Magento/Quote/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..08e87c7db9ec7 --- /dev/null +++ b/app/code/Magento/Quote/etc/adminhtml/di.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Quote\Model\ValidationRules\QuoteValidationComposite"> + <arguments> + <argument name="validationRules" xsi:type="array"> + <item name="ShippingAddressValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\ShippingAddressValidationRule</item> + <item name="AllowedCountryValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\AllowedCountryValidationRule</item> + <item name="ShippingMethodValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\ShippingMethodValidationRule</item> + <item name="BillingAddressValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\BillingAddressValidationRule</item> + <item name="PaymentMethodValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\PaymentMethodValidationRule</item> + </argument> + </arguments> + </type> +</config> \ No newline at end of file diff --git a/app/code/Magento/Quote/etc/db_schema_whitelist.json b/app/code/Magento/Quote/etc/db_schema_whitelist.json index f7898c9d15dc0..c2cc34293dcb5 100644 --- a/app/code/Magento/Quote/etc/db_schema_whitelist.json +++ b/app/code/Magento/Quote/etc/db_schema_whitelist.json @@ -1,329 +1,329 @@ { - "quote": { - "column": { - "entity_id": true, - "store_id": true, - "created_at": true, - "updated_at": true, - "converted_at": true, - "is_active": true, - "is_virtual": true, - "is_multi_shipping": true, - "items_count": true, - "items_qty": true, - "orig_order_id": true, - "store_to_base_rate": true, - "store_to_quote_rate": true, - "base_currency_code": true, - "store_currency_code": true, - "quote_currency_code": true, - "grand_total": true, - "base_grand_total": true, - "checkout_method": true, - "customer_id": true, - "customer_tax_class_id": true, - "customer_group_id": true, - "customer_email": true, - "customer_prefix": true, - "customer_firstname": true, - "customer_middlename": true, - "customer_lastname": true, - "customer_suffix": true, - "customer_dob": true, - "customer_note": true, - "customer_note_notify": true, - "customer_is_guest": true, - "remote_ip": true, - "applied_rule_ids": true, - "reserved_order_id": true, - "password_hash": true, - "coupon_code": true, - "global_currency_code": true, - "base_to_global_rate": true, - "base_to_quote_rate": true, - "customer_taxvat": true, - "customer_gender": true, - "subtotal": true, - "base_subtotal": true, - "subtotal_with_discount": true, - "base_subtotal_with_discount": true, - "is_changed": true, - "trigger_recollect": true, - "ext_shipping_info": true + "quote": { + "column": { + "entity_id": true, + "store_id": true, + "created_at": true, + "updated_at": true, + "converted_at": true, + "is_active": true, + "is_virtual": true, + "is_multi_shipping": true, + "items_count": true, + "items_qty": true, + "orig_order_id": true, + "store_to_base_rate": true, + "store_to_quote_rate": true, + "base_currency_code": true, + "store_currency_code": true, + "quote_currency_code": true, + "grand_total": true, + "base_grand_total": true, + "checkout_method": true, + "customer_id": true, + "customer_tax_class_id": true, + "customer_group_id": true, + "customer_email": true, + "customer_prefix": true, + "customer_firstname": true, + "customer_middlename": true, + "customer_lastname": true, + "customer_suffix": true, + "customer_dob": true, + "customer_note": true, + "customer_note_notify": true, + "customer_is_guest": true, + "remote_ip": true, + "applied_rule_ids": true, + "reserved_order_id": true, + "password_hash": true, + "coupon_code": true, + "global_currency_code": true, + "base_to_global_rate": true, + "base_to_quote_rate": true, + "customer_taxvat": true, + "customer_gender": true, + "subtotal": true, + "base_subtotal": true, + "subtotal_with_discount": true, + "base_subtotal_with_discount": true, + "is_changed": true, + "trigger_recollect": true, + "ext_shipping_info": true + }, + "index": { + "QUOTE_CUSTOMER_ID_STORE_ID_IS_ACTIVE": true, + "QUOTE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_STORE_ID_STORE_STORE_ID": true + } }, - "index": { - "QUOTE_CUSTOMER_ID_STORE_ID_IS_ACTIVE": true, - "QUOTE_STORE_ID": true + "quote_address": { + "column": { + "address_id": true, + "quote_id": true, + "created_at": true, + "updated_at": true, + "customer_id": true, + "save_in_address_book": true, + "customer_address_id": true, + "address_type": true, + "email": true, + "prefix": true, + "firstname": true, + "middlename": true, + "lastname": true, + "suffix": true, + "company": true, + "street": true, + "city": true, + "region": true, + "region_id": true, + "postcode": true, + "country_id": true, + "telephone": true, + "fax": true, + "same_as_billing": true, + "collect_shipping_rates": true, + "shipping_method": true, + "shipping_description": true, + "weight": true, + "subtotal": true, + "base_subtotal": true, + "subtotal_with_discount": true, + "base_subtotal_with_discount": true, + "tax_amount": true, + "base_tax_amount": true, + "shipping_amount": true, + "base_shipping_amount": true, + "shipping_tax_amount": true, + "base_shipping_tax_amount": true, + "discount_amount": true, + "base_discount_amount": true, + "grand_total": true, + "base_grand_total": true, + "customer_notes": true, + "applied_taxes": true, + "discount_description": true, + "shipping_discount_amount": true, + "base_shipping_discount_amount": true, + "subtotal_incl_tax": true, + "base_subtotal_total_incl_tax": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "shipping_discount_tax_compensation_amount": true, + "base_shipping_discount_tax_compensation_amnt": true, + "shipping_incl_tax": true, + "base_shipping_incl_tax": true, + "vat_id": true, + "vat_is_valid": true, + "vat_request_id": true, + "vat_request_date": true, + "vat_request_success": true + }, + "index": { + "QUOTE_ADDRESS_QUOTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_ADDRESS_QUOTE_ID_QUOTE_ENTITY_ID": true + } }, - "constraint": { - "PRIMARY": true, - "QUOTE_STORE_ID_STORE_STORE_ID": true - } - }, - "quote_address": { - "column": { - "address_id": true, - "quote_id": true, - "created_at": true, - "updated_at": true, - "customer_id": true, - "save_in_address_book": true, - "customer_address_id": true, - "address_type": true, - "email": true, - "prefix": true, - "firstname": true, - "middlename": true, - "lastname": true, - "suffix": true, - "company": true, - "street": true, - "city": true, - "region": true, - "region_id": true, - "postcode": true, - "country_id": true, - "telephone": true, - "fax": true, - "same_as_billing": true, - "collect_shipping_rates": true, - "shipping_method": true, - "shipping_description": true, - "weight": true, - "subtotal": true, - "base_subtotal": true, - "subtotal_with_discount": true, - "base_subtotal_with_discount": true, - "tax_amount": true, - "base_tax_amount": true, - "shipping_amount": true, - "base_shipping_amount": true, - "shipping_tax_amount": true, - "base_shipping_tax_amount": true, - "discount_amount": true, - "base_discount_amount": true, - "grand_total": true, - "base_grand_total": true, - "customer_notes": true, - "applied_taxes": true, - "discount_description": true, - "shipping_discount_amount": true, - "base_shipping_discount_amount": true, - "subtotal_incl_tax": true, - "base_subtotal_total_incl_tax": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "shipping_discount_tax_compensation_amount": true, - "base_shipping_discount_tax_compensation_amnt": true, - "shipping_incl_tax": true, - "base_shipping_incl_tax": true, - "vat_id": true, - "vat_is_valid": true, - "vat_request_id": true, - "vat_request_date": true, - "vat_request_success": true - }, - "index": { - "QUOTE_ADDRESS_QUOTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "QUOTE_ADDRESS_QUOTE_ID_QUOTE_ENTITY_ID": true - } - }, - "quote_item": { - "column": { - "item_id": true, - "quote_id": true, - "created_at": true, - "updated_at": true, - "product_id": true, - "store_id": true, - "parent_item_id": true, - "is_virtual": true, - "sku": true, - "name": true, - "description": true, - "applied_rule_ids": true, - "additional_data": true, - "is_qty_decimal": true, - "no_discount": true, - "weight": true, - "qty": true, - "price": true, - "base_price": true, - "custom_price": true, - "discount_percent": true, - "discount_amount": true, - "base_discount_amount": true, - "tax_percent": true, - "tax_amount": true, - "base_tax_amount": true, - "row_total": true, - "base_row_total": true, - "row_total_with_discount": true, - "row_weight": true, - "product_type": true, - "base_tax_before_discount": true, - "tax_before_discount": true, - "original_custom_price": true, - "redirect_url": true, - "base_cost": true, - "price_incl_tax": true, - "base_price_incl_tax": true, - "row_total_incl_tax": true, - "base_row_total_incl_tax": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true - }, - "index": { - "QUOTE_ITEM_PARENT_ITEM_ID": true, - "QUOTE_ITEM_PRODUCT_ID": true, - "QUOTE_ITEM_QUOTE_ID": true, - "QUOTE_ITEM_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "QUOTE_ITEM_PARENT_ITEM_ID_QUOTE_ITEM_ITEM_ID": true, - "QUOTE_ITEM_QUOTE_ID_QUOTE_ENTITY_ID": true, - "QUOTE_ITEM_STORE_ID_STORE_STORE_ID": true - } - }, - "quote_address_item": { - "column": { - "address_item_id": true, - "parent_item_id": true, - "quote_address_id": true, - "quote_item_id": true, - "created_at": true, - "updated_at": true, - "applied_rule_ids": true, - "additional_data": true, - "weight": true, - "qty": true, - "discount_amount": true, - "tax_amount": true, - "row_total": true, - "base_row_total": true, - "row_total_with_discount": true, - "base_discount_amount": true, - "base_tax_amount": true, - "row_weight": true, - "product_id": true, - "super_product_id": true, - "parent_product_id": true, - "sku": true, - "image": true, - "name": true, - "description": true, - "is_qty_decimal": true, - "price": true, - "discount_percent": true, - "no_discount": true, - "tax_percent": true, - "base_price": true, - "base_cost": true, - "price_incl_tax": true, - "base_price_incl_tax": true, - "row_total_incl_tax": true, - "base_row_total_incl_tax": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true - }, - "index": { - "QUOTE_ADDRESS_ITEM_QUOTE_ADDRESS_ID": true, - "QUOTE_ADDRESS_ITEM_PARENT_ITEM_ID": true, - "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID": true + "quote_item": { + "column": { + "item_id": true, + "quote_id": true, + "created_at": true, + "updated_at": true, + "product_id": true, + "store_id": true, + "parent_item_id": true, + "is_virtual": true, + "sku": true, + "name": true, + "description": true, + "applied_rule_ids": true, + "additional_data": true, + "is_qty_decimal": true, + "no_discount": true, + "weight": true, + "qty": true, + "price": true, + "base_price": true, + "custom_price": true, + "discount_percent": true, + "discount_amount": true, + "base_discount_amount": true, + "tax_percent": true, + "tax_amount": true, + "base_tax_amount": true, + "row_total": true, + "base_row_total": true, + "row_total_with_discount": true, + "row_weight": true, + "product_type": true, + "base_tax_before_discount": true, + "tax_before_discount": true, + "original_custom_price": true, + "redirect_url": true, + "base_cost": true, + "price_incl_tax": true, + "base_price_incl_tax": true, + "row_total_incl_tax": true, + "base_row_total_incl_tax": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true + }, + "index": { + "QUOTE_ITEM_PARENT_ITEM_ID": true, + "QUOTE_ITEM_PRODUCT_ID": true, + "QUOTE_ITEM_QUOTE_ID": true, + "QUOTE_ITEM_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_ITEM_PARENT_ITEM_ID_QUOTE_ITEM_ITEM_ID": true, + "QUOTE_ITEM_QUOTE_ID_QUOTE_ENTITY_ID": true, + "QUOTE_ITEM_STORE_ID_STORE_STORE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "QUOTE_ADDRESS_ITEM_QUOTE_ADDRESS_ID_QUOTE_ADDRESS_ADDRESS_ID": true, - "QUOTE_ADDR_ITEM_PARENT_ITEM_ID_QUOTE_ADDR_ITEM_ADDR_ITEM_ID": true, - "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID_QUOTE_ITEM_ITEM_ID": true - } - }, - "quote_item_option": { - "column": { - "option_id": true, - "item_id": true, - "product_id": true, - "code": true, - "value": true - }, - "index": { - "QUOTE_ITEM_OPTION_ITEM_ID": true - }, - "constraint": { - "PRIMARY": true, - "QUOTE_ITEM_OPTION_ITEM_ID_QUOTE_ITEM_ITEM_ID": true - } - }, - "quote_payment": { - "column": { - "payment_id": true, - "quote_id": true, - "created_at": true, - "updated_at": true, - "method": true, - "cc_type": true, - "cc_number_enc": true, - "cc_last_4": true, - "cc_cid_enc": true, - "cc_owner": true, - "cc_exp_month": true, - "cc_exp_year": true, - "cc_ss_owner": true, - "cc_ss_start_month": true, - "cc_ss_start_year": true, - "po_number": true, - "additional_data": true, - "cc_ss_issue": true, - "additional_information": true + "quote_address_item": { + "column": { + "address_item_id": true, + "parent_item_id": true, + "quote_address_id": true, + "quote_item_id": true, + "created_at": true, + "updated_at": true, + "applied_rule_ids": true, + "additional_data": true, + "weight": true, + "qty": true, + "discount_amount": true, + "tax_amount": true, + "row_total": true, + "base_row_total": true, + "row_total_with_discount": true, + "base_discount_amount": true, + "base_tax_amount": true, + "row_weight": true, + "product_id": true, + "super_product_id": true, + "parent_product_id": true, + "sku": true, + "image": true, + "name": true, + "description": true, + "is_qty_decimal": true, + "price": true, + "discount_percent": true, + "no_discount": true, + "tax_percent": true, + "base_price": true, + "base_cost": true, + "price_incl_tax": true, + "base_price_incl_tax": true, + "row_total_incl_tax": true, + "base_row_total_incl_tax": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true + }, + "index": { + "QUOTE_ADDRESS_ITEM_QUOTE_ADDRESS_ID": true, + "QUOTE_ADDRESS_ITEM_PARENT_ITEM_ID": true, + "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_ADDRESS_ITEM_QUOTE_ADDRESS_ID_QUOTE_ADDRESS_ADDRESS_ID": true, + "QUOTE_ADDR_ITEM_PARENT_ITEM_ID_QUOTE_ADDR_ITEM_ADDR_ITEM_ID": true, + "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID_QUOTE_ITEM_ITEM_ID": true + } }, - "index": { - "QUOTE_PAYMENT_QUOTE_ID": true + "quote_item_option": { + "column": { + "option_id": true, + "item_id": true, + "product_id": true, + "code": true, + "value": true + }, + "index": { + "QUOTE_ITEM_OPTION_ITEM_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_ITEM_OPTION_ITEM_ID_QUOTE_ITEM_ITEM_ID": true + } }, - "constraint": { - "PRIMARY": true, - "QUOTE_PAYMENT_QUOTE_ID_QUOTE_ENTITY_ID": true - } - }, - "quote_shipping_rate": { - "column": { - "rate_id": true, - "address_id": true, - "created_at": true, - "updated_at": true, - "carrier": true, - "carrier_title": true, - "code": true, - "method": true, - "method_description": true, - "price": true, - "error_message": true, - "method_title": true - }, - "index": { - "QUOTE_SHIPPING_RATE_ADDRESS_ID": true - }, - "constraint": { - "PRIMARY": true, - "QUOTE_SHIPPING_RATE_ADDRESS_ID_QUOTE_ADDRESS_ADDRESS_ID": true - } - }, - "quote_id_mask": { - "column": { - "entity_id": true, - "quote_id": true, - "masked_id": true + "quote_payment": { + "column": { + "payment_id": true, + "quote_id": true, + "created_at": true, + "updated_at": true, + "method": true, + "cc_type": true, + "cc_number_enc": true, + "cc_last_4": true, + "cc_cid_enc": true, + "cc_owner": true, + "cc_exp_month": true, + "cc_exp_year": true, + "cc_ss_owner": true, + "cc_ss_start_month": true, + "cc_ss_start_year": true, + "po_number": true, + "additional_data": true, + "cc_ss_issue": true, + "additional_information": true + }, + "index": { + "QUOTE_PAYMENT_QUOTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_PAYMENT_QUOTE_ID_QUOTE_ENTITY_ID": true + } }, - "index": { - "QUOTE_ID_MASK_QUOTE_ID": true, - "QUOTE_ID_MASK_MASKED_ID": true + "quote_shipping_rate": { + "column": { + "rate_id": true, + "address_id": true, + "created_at": true, + "updated_at": true, + "carrier": true, + "carrier_title": true, + "code": true, + "method": true, + "method_description": true, + "price": true, + "error_message": true, + "method_title": true + }, + "index": { + "QUOTE_SHIPPING_RATE_ADDRESS_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_SHIPPING_RATE_ADDRESS_ID_QUOTE_ADDRESS_ADDRESS_ID": true + } }, - "constraint": { - "PRIMARY": true, - "QUOTE_ID_MASK_QUOTE_ID_QUOTE_ENTITY_ID": true + "quote_id_mask": { + "column": { + "entity_id": true, + "quote_id": true, + "masked_id": true + }, + "index": { + "QUOTE_ID_MASK_QUOTE_ID": true, + "QUOTE_ID_MASK_MASKED_ID": true + }, + "constraint": { + "PRIMARY": true, + "QUOTE_ID_MASK_QUOTE_ID_QUOTE_ENTITY_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml index aa6fafb5dd051..ecc6426855679 100644 --- a/app/code/Magento/Quote/etc/di.xml +++ b/app/code/Magento/Quote/etc/di.xml @@ -41,6 +41,7 @@ <preference for="Magento\Quote\Api\GuestCartTotalManagementInterface" type="Magento\Quote\Model\GuestCart\GuestCartTotalManagement" /> <preference for="Magento\Quote\Api\Data\EstimateAddressInterface" type="Magento\Quote\Model\EstimateAddress" /> <preference for="Magento\Quote\Api\Data\ProductOptionInterface" type="Magento\Quote\Model\Quote\ProductOption" /> + <preference for="Magento\Quote\Model\ValidationRules\QuoteValidationRuleInterface" type="Magento\Quote\Model\ValidationRules\QuoteValidationComposite\Proxy"/> <type name="Magento\Webapi\Controller\Rest\ParamsOverrider"> <arguments> <argument name="paramOverriders" xsi:type="array"> @@ -95,4 +96,41 @@ <plugin name="clean_quote_items_after_product_delete" type="Magento\Quote\Model\Product\Plugin\RemoveQuoteItems"/> <plugin name="update_quote_items_after_product_save" type="Magento\Quote\Model\Product\Plugin\UpdateQuoteItems"/> </type> + <type name="Magento\Quote\Model\ValidationRules\QuoteValidationComposite"> + <arguments> + <argument name="validationRules" xsi:type="array"> + <item name="AllowedCountryValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\AllowedCountryValidationRule</item> + <item name="ShippingAddressValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\ShippingAddressValidationRule</item> + <item name="ShippingMethodValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\ShippingMethodValidationRule</item> + <item name="BillingAddressValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\BillingAddressValidationRule</item> + <item name="PaymentMethodValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\PaymentMethodValidationRule</item> + <item name="MinimumAmountValidationRule" xsi:type="object">Magento\Quote\Model\ValidationRules\MinimumAmountValidationRule</item> + </argument> + </arguments> + </type> + <type name="Magento\Quote\Model\ValidationRules\AllowedCountryValidationRule"> + <arguments> + <argument name="generalMessage" xsi:type="string" translatable="true">Some addresses can't be used due to the configurations for specific countries.</argument> + </arguments> + </type> + <type name="Magento\Quote\Model\ValidationRules\ShippingAddressValidationRule"> + <arguments> + <argument name="generalMessage" xsi:type="string" translatable="true">Please check the shipping address information.</argument> + </arguments> + </type> + <type name="Magento\Quote\Model\ValidationRules\ShippingMethodValidationRule"> + <arguments> + <argument name="generalMessage" xsi:type="string" translatable="true">The shipping method is missing. Select the shipping method and try again.</argument> + </arguments> + </type> + <type name="Magento\Quote\Model\ValidationRules\BillingAddressValidationRule"> + <arguments> + <argument name="generalMessage" xsi:type="string" translatable="true">Please check the billing address information.</argument> + </arguments> + </type> + <type name="Magento\Quote\Model\ValidationRules\PaymentMethodValidationRule"> + <arguments> + <argument name="generalMessage" xsi:type="string" translatable="true">Enter a valid payment method and try again.</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Quote/etc/frontend/di.xml b/app/code/Magento/Quote/etc/frontend/di.xml new file mode 100644 index 0000000000000..25acd6763ba56 --- /dev/null +++ b/app/code/Magento/Quote/etc/frontend/di.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Quote\Plugin\UpdateQuoteStore"> + <arguments> + <argument name="quoteRepository" xsi:type="object">Magento\Quote\Model\QuoteRepository\Proxy</argument> + <argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument> + </arguments> + </type> + <type name="Magento\Store\Api\StoreCookieManagerInterface"> + <plugin name="update_quote_store_after_switch_store_view" type="Magento\Quote\Plugin\UpdateQuoteStore"/> + </type> +</config> diff --git a/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json b/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 4f0f8f2c07344..0000000000000 --- a/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-quote-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ReleaseNotification/README.md b/app/code/Magento/ReleaseNotification/README.md index e0d6e6de17c60..df3206a176f09 100644 --- a/app/code/Magento/ReleaseNotification/README.md +++ b/app/code/Magento/ReleaseNotification/README.md @@ -46,7 +46,7 @@ Each modal page can have the following optional content: The Sub Heading section is ideally used on the first modal page as a way to describe one to three highlighted features that will be presented in greater detail on the following modal pages. It is recommended to use the Main Content -> Text Body and Bullet Point lists as the paragraph and list content displayed on a highlighted feature's detail modal page. -A clickable link to internal or external content in any text field will be created by using the following format and opened in a new browser tab. Providing the URL for the link followed by the the text to be displayed for that link in brackets will cause a clickable link to be created. The text between the brackets [text] will be the text that the clickable link shows. +A clickable link to internal or external content in any text field will be created by using the following format and opened in a new browser tab. Providing the URL for the link followed by the text to be displayed for that link in brackets will cause a clickable link to be created. The text between the brackets [text] will be the text that the clickable link shows. ### Link Format Example: diff --git a/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json b/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json deleted file mode 100644 index 9499e710aaed2..0000000000000 --- a/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-release-notification", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ReleaseNotification/etc/db_schema_whitelist.json b/app/code/Magento/ReleaseNotification/etc/db_schema_whitelist.json index d44e071b00c7c..5edb2b7010be6 100644 --- a/app/code/Magento/ReleaseNotification/etc/db_schema_whitelist.json +++ b/app/code/Magento/ReleaseNotification/etc/db_schema_whitelist.json @@ -1,14 +1,14 @@ { - "release_notification_viewer_log": { - "column": { - "id": true, - "viewer_id": true, - "last_view_version": true - }, - "constraint": { - "PRIMARY": true, - "RELEASE_NOTIFICATION_VIEWER_LOG_VIEWER_ID_ADMIN_USER_USER_ID": true, - "RELEASE_NOTIFICATION_VIEWER_LOG_VIEWER_ID": true + "release_notification_viewer_log": { + "column": { + "id": true, + "viewer_id": true, + "last_view_version": true + }, + "constraint": { + "PRIMARY": true, + "RELEASE_NOTIFICATION_VIEWER_LOG_VIEWER_ID_ADMIN_USER_USER_ID": true, + "RELEASE_NOTIFICATION_VIEWER_LOG_VIEWER_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php b/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php index 5a27b4dc7666f..4ac12501aa90f 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Config/Form/Field/YtdStart.php @@ -23,9 +23,8 @@ protected function _getElementHtml(AbstractElement $element) { $_months = []; for ($i = 1; $i <= 12; $i++) { - $_months[$i] = $this->_localeDate->date(mktime(null, null, null, $i))->format('m'); + $_months[$i] = $this->_localeDate->date(mktime(null, null, null, $i, 1))->format('m'); } - $_days = []; for ($i = 1; $i <= 31; $i++) { $_days[$i] = $i < 10 ? '0' . $i : $i; diff --git a/app/code/Magento/Reports/Block/Adminhtml/Grid/AbstractGrid.php b/app/code/Magento/Reports/Block/Adminhtml/Grid/AbstractGrid.php index 158455db26455..be7dfa70efbb4 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Grid/AbstractGrid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Grid/AbstractGrid.php @@ -302,6 +302,7 @@ public function getCountTotals() ); $this->_addOrderStatusFilter($totalsCollection, $filterData); + $this->_addCustomFilter($totalsCollection, $filterData); if ($totalsCollection->load()->getSize() < 1 || !$filterData->getData('from')) { $this->setTotals(new \Magento\Framework\DataObject()); @@ -313,6 +314,7 @@ public function getCountTotals() } } } + return parent::getCountTotals(); } diff --git a/app/code/Magento/Reports/Block/Adminhtml/Product/Viewed.php b/app/code/Magento/Reports/Block/Adminhtml/Product/Viewed.php index fc4cffbdca408..f901b32d8b12f 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Product/Viewed.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Product/Viewed.php @@ -17,7 +17,7 @@ class Viewed extends \Magento\Backend\Block\Widget\Grid\Container /** * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * @return void diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Bestsellers.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Bestsellers.php index d70930d2395ae..b773184408a7f 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Bestsellers.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Bestsellers.php @@ -19,7 +19,7 @@ class Bestsellers extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons.php index b8f71158877bb..fe85af58b34f6 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Coupons.php @@ -19,7 +19,7 @@ class Coupons extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced.php index c96483e33ebe5..57594a11bd997 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Invoiced.php @@ -19,7 +19,7 @@ class Invoiced extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Refunded.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Refunded.php index 7ff80f62f6bee..994b29e6eb0dd 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Refunded.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Refunded.php @@ -19,7 +19,7 @@ class Refunded extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales.php index 5abea45e657d7..64375ace3e94d 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales.php @@ -19,7 +19,7 @@ class Sales extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Shipping.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Shipping.php index 44dd4521c7bbe..e4dbdc2737745 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Shipping.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Shipping.php @@ -19,7 +19,7 @@ class Shipping extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax.php index 38de08314d257..fa9e63745a87d 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Tax.php @@ -19,7 +19,7 @@ class Tax extends \Magento\Backend\Block\Widget\Grid\Container * * @var string */ - protected $_template = 'report/grid/container.phtml'; + protected $_template = 'Magento_Reports::report/grid/container.phtml'; /** * {@inheritdoc} diff --git a/app/code/Magento/Reports/Block/Adminhtml/Wishlist.php b/app/code/Magento/Reports/Block/Adminhtml/Wishlist.php index 28f2011de3365..1ca76cb1cf95f 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Wishlist.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Wishlist.php @@ -18,7 +18,7 @@ class Wishlist extends \Magento\Backend\Block\Template * * @var string */ - protected $_template = 'report/wishlist.phtml'; + protected $_template = 'Magento_Reports::report/wishlist.phtml'; /** * Reports wishlist collection factory diff --git a/app/code/Magento/Reports/Test/Mftf/composer.json b/app/code/Magento/Reports/Test/Mftf/composer.json deleted file mode 100644 index 6801ad5adaf2e..0000000000000 --- a/app/code/Magento/Reports/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-reports", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-review": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php new file mode 100644 index 0000000000000..3c7ad9ee9e686 --- /dev/null +++ b/app/code/Magento/Reports/Test/Unit/Block/Adminhtml/Sales/Coupons/GridTest.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Reports\Test\Unit\Block\Adminhtml\Sales\Coupons; + +/** + * Test for class \Magento\Reports\Block\Adminhtml\Sales\Coupons\Grid + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Reports\Block\Adminhtml\Sales\Coupons\Grid + */ + private $model; + + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var \Magento\Reports\Model\ResourceModel\Report\Collection\Factory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resourceFactoryMock; + + /** + * Set up mock objects for tested class + * + * @return void + */ + protected function setUp(): void + { + $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->getMock(); + $this->resourceFactoryMock = $this + ->getMockBuilder(\Magento\Reports\Model\ResourceModel\Report\Collection\Factory::class) + ->disableOriginalConstructor() + ->getMock(); + $aggregatedColumns = [1 => 'SUM(value)']; + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Reports\Block\Adminhtml\Sales\Coupons\Grid::class, + [ + '_storeManager' => $this->storeManagerMock, + '_aggregatedColumns' => $aggregatedColumns, + 'resourceFactory' => $this->resourceFactoryMock, + ] + ); + } + + /** + * @dataProvider getCountTotalsDataProvider + * + * @param string $reportType + * @param int $priceRuleType + * @param int $collectionSize + * @param bool $expectedCountTotals + * @return void + */ + public function testGetCountTotals( + string $reportType, + int $priceRuleType, + int $collectionSize, + bool $expectedCountTotals + ): void { + $filterData = new \Magento\Framework\DataObject(); + $filterData->setData('report_type', $reportType); + $filterData->setData('period_type', 'day'); + $filterData->setData('from', '2000-01-01'); + $filterData->setData('to', '2000-01-30'); + $filterData->setData('store_ids', '1'); + $filterData->setData('price_rule_type', $priceRuleType); + if ($priceRuleType) { + $filterData->setData('rules_list', ['0,1']); + } + $filterData->setData('order_statuses', 'statuses'); + $this->model->setFilterData($filterData); + + $resourceCollectionName = $this->model->getResourceCollectionName(); + $collectionMock = $this->buildBaseCollectionMock($filterData, $resourceCollectionName, $collectionSize); + + $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->getMock(); + $this->storeManagerMock->method('getStores') + ->willReturn([1 => $store]); + $this->resourceFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($collectionMock); + + $this->assertEquals($expectedCountTotals, $this->model->getCountTotals()); + } + + /** + * @return array + */ + public function getCountTotalsDataProvider(): array + { + return [ + ['created_at_shipment', 0, 0, false], + ['created_at_shipment', 0, 1, true], + ['updated_at_order', 0, 1, true], + ['updated_at_order', 1, 1, true], + ]; + } + + /** + * @param \Magento\Framework\DataObject $filterData + * @param string $resourceCollectionName + * @param int $collectionSize + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function buildBaseCollectionMock( + \Magento\Framework\DataObject $filterData, + string $resourceCollectionName, + int $collectionSize + ): \PHPUnit_Framework_MockObject_MockObject { + $collectionMock = $this->getMockBuilder($resourceCollectionName) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('setPeriod') + ->with($filterData->getData('period_type')) + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('setDateRange') + ->with($filterData->getData('from'), $filterData->getData('to')) + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('addStoreFilter') + ->with(\explode(',', $filterData->getData('store_ids'))) + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('setAggregatedColumns') + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('isTotals') + ->with(true) + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('addOrderStatusFilter') + ->with($filterData->getData('order_statuses')) + ->willReturnSelf(); + + if ($filterData->getData('price_rule_type')) { + $collectionMock->expects($this->once()) + ->method('addRuleFilter') + ->with(\explode(',', $filterData->getData('rules_list')[0])) + ->willReturnSelf(); + } + + $collectionMock->expects($this->once()) + ->method('load') + ->willReturnSelf(); + $collectionMock->expects($this->once()) + ->method('getSize') + ->willReturn($collectionSize); + if ($collectionSize) { + $itemMock = $this->getMockBuilder(\Magento\Reports\Model\Item::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$itemMock]); + } + + return $collectionMock; + } +} diff --git a/app/code/Magento/Reports/etc/db_schema_whitelist.json b/app/code/Magento/Reports/etc/db_schema_whitelist.json index 458b2ea8fa803..8f55ef4fdbee6 100644 --- a/app/code/Magento/Reports/etc/db_schema_whitelist.json +++ b/app/code/Magento/Reports/etc/db_schema_whitelist.json @@ -1,152 +1,152 @@ { - "report_compared_product_index": { - "column": { - "index_id": true, - "visitor_id": true, - "customer_id": true, - "product_id": true, - "store_id": true, - "added_at": true + "report_compared_product_index": { + "column": { + "index_id": true, + "visitor_id": true, + "customer_id": true, + "product_id": true, + "store_id": true, + "added_at": true + }, + "index": { + "REPORT_COMPARED_PRODUCT_INDEX_STORE_ID": true, + "REPORT_COMPARED_PRODUCT_INDEX_ADDED_AT": true, + "REPORT_COMPARED_PRODUCT_INDEX_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_CMPD_PRD_IDX_CSTR_ID_CSTR_ENTT_ENTT_ID": true, + "REPORT_CMPD_PRD_IDX_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "REPORT_COMPARED_PRODUCT_INDEX_STORE_ID_STORE_STORE_ID": true, + "REPORT_COMPARED_PRODUCT_INDEX_VISITOR_ID_PRODUCT_ID": true, + "REPORT_COMPARED_PRODUCT_INDEX_CUSTOMER_ID_PRODUCT_ID": true, + "REPORT_CMPD_PRD_IDX_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } }, - "index": { - "REPORT_COMPARED_PRODUCT_INDEX_STORE_ID": true, - "REPORT_COMPARED_PRODUCT_INDEX_ADDED_AT": true, - "REPORT_COMPARED_PRODUCT_INDEX_PRODUCT_ID": true + "report_viewed_product_index": { + "column": { + "index_id": true, + "visitor_id": true, + "customer_id": true, + "product_id": true, + "store_id": true, + "added_at": true + }, + "index": { + "REPORT_VIEWED_PRODUCT_INDEX_STORE_ID": true, + "REPORT_VIEWED_PRODUCT_INDEX_ADDED_AT": true, + "REPORT_VIEWED_PRODUCT_INDEX_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_VIEWED_PRD_IDX_CSTR_ID_CSTR_ENTT_ENTT_ID": true, + "REPORT_VIEWED_PRD_IDX_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "REPORT_VIEWED_PRODUCT_INDEX_STORE_ID_STORE_STORE_ID": true, + "REPORT_VIEWED_PRODUCT_INDEX_VISITOR_ID_PRODUCT_ID": true, + "REPORT_VIEWED_PRODUCT_INDEX_CUSTOMER_ID_PRODUCT_ID": true, + "REPORT_VIEWED_PRD_IDX_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } }, - "constraint": { - "PRIMARY": true, - "REPORT_CMPD_PRD_IDX_CSTR_ID_CSTR_ENTT_ENTT_ID": true, - "REPORT_CMPD_PRD_IDX_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "REPORT_COMPARED_PRODUCT_INDEX_STORE_ID_STORE_STORE_ID": true, - "REPORT_COMPARED_PRODUCT_INDEX_VISITOR_ID_PRODUCT_ID": true, - "REPORT_COMPARED_PRODUCT_INDEX_CUSTOMER_ID_PRODUCT_ID": true, - "REPORT_CMPD_PRD_IDX_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "report_viewed_product_index": { - "column": { - "index_id": true, - "visitor_id": true, - "customer_id": true, - "product_id": true, - "store_id": true, - "added_at": true - }, - "index": { - "REPORT_VIEWED_PRODUCT_INDEX_STORE_ID": true, - "REPORT_VIEWED_PRODUCT_INDEX_ADDED_AT": true, - "REPORT_VIEWED_PRODUCT_INDEX_PRODUCT_ID": true + "report_event_types": { + "column": { + "event_type_id": true, + "event_name": true, + "customer_login": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true, - "REPORT_VIEWED_PRD_IDX_CSTR_ID_CSTR_ENTT_ENTT_ID": true, - "REPORT_VIEWED_PRD_IDX_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "REPORT_VIEWED_PRODUCT_INDEX_STORE_ID_STORE_STORE_ID": true, - "REPORT_VIEWED_PRODUCT_INDEX_VISITOR_ID_PRODUCT_ID": true, - "REPORT_VIEWED_PRODUCT_INDEX_CUSTOMER_ID_PRODUCT_ID": true, - "REPORT_VIEWED_PRD_IDX_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "report_event_types": { - "column": { - "event_type_id": true, - "event_name": true, - "customer_login": true + "report_event": { + "column": { + "event_id": true, + "logged_at": true, + "event_type_id": true, + "object_id": true, + "subject_id": true, + "subtype": true, + "store_id": true + }, + "index": { + "REPORT_EVENT_EVENT_TYPE_ID": true, + "REPORT_EVENT_SUBJECT_ID": true, + "REPORT_EVENT_OBJECT_ID": true, + "REPORT_EVENT_SUBTYPE": true, + "REPORT_EVENT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_EVENT_STORE_ID_STORE_STORE_ID": true, + "REPORT_EVENT_EVENT_TYPE_ID_REPORT_EVENT_TYPES_EVENT_TYPE_ID": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "report_event": { - "column": { - "event_id": true, - "logged_at": true, - "event_type_id": true, - "object_id": true, - "subject_id": true, - "subtype": true, - "store_id": true - }, - "index": { - "REPORT_EVENT_EVENT_TYPE_ID": true, - "REPORT_EVENT_SUBJECT_ID": true, - "REPORT_EVENT_OBJECT_ID": true, - "REPORT_EVENT_SUBTYPE": true, - "REPORT_EVENT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "REPORT_EVENT_STORE_ID_STORE_STORE_ID": true, - "REPORT_EVENT_EVENT_TYPE_ID_REPORT_EVENT_TYPES_EVENT_TYPE_ID": true - } - }, - "report_viewed_product_aggregated_daily": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "views_num": true, - "rating_pos": true - }, - "index": { - "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_STORE_ID": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_STORE_ID_STORE_STORE_ID": true, - "REPORT_VIEWED_PRD_AGGRED_DAILY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "REPORT_VIEWED_PRD_AGGRED_DAILY_PERIOD_STORE_ID_PRD_ID": true, - "REPORT_VIEWED_PRD_AGGRED_DAILY_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true - } - }, - "report_viewed_product_aggregated_monthly": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "views_num": true, - "rating_pos": true - }, - "index": { - "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_STORE_ID": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_STORE_ID_STORE_STORE_ID": true, - "REPORT_VIEWED_PRD_AGGRED_MONTHLY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "REPORT_VIEWED_PRD_AGGRED_MONTHLY_PERIOD_STORE_ID_PRD_ID": true, - "FK_0140003A30AFC1A9188D723C4634BA5D": true - } - }, - "report_viewed_product_aggregated_yearly": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "views_num": true, - "rating_pos": true + "report_viewed_product_aggregated_daily": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "views_num": true, + "rating_pos": true + }, + "index": { + "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_STORE_ID": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_DAILY_STORE_ID_STORE_STORE_ID": true, + "REPORT_VIEWED_PRD_AGGRED_DAILY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "REPORT_VIEWED_PRD_AGGRED_DAILY_PERIOD_STORE_ID_PRD_ID": true, + "REPORT_VIEWED_PRD_AGGRED_DAILY_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } }, - "index": { - "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_STORE_ID": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_PRODUCT_ID": true + "report_viewed_product_aggregated_monthly": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "views_num": true, + "rating_pos": true + }, + "index": { + "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_STORE_ID": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_MONTHLY_STORE_ID_STORE_STORE_ID": true, + "REPORT_VIEWED_PRD_AGGRED_MONTHLY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "REPORT_VIEWED_PRD_AGGRED_MONTHLY_PERIOD_STORE_ID_PRD_ID": true, + "FK_0140003A30AFC1A9188D723C4634BA5D": true + } }, - "constraint": { - "PRIMARY": true, - "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_STORE_ID_STORE_STORE_ID": true, - "REPORT_VIEWED_PRD_AGGRED_YEARLY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, - "REPORT_VIEWED_PRD_AGGRED_YEARLY_PERIOD_STORE_ID_PRD_ID": true, - "REPORT_VIEWED_PRD_AGGRED_YEARLY_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + "report_viewed_product_aggregated_yearly": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "views_num": true, + "rating_pos": true + }, + "index": { + "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_STORE_ID": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "REPORT_VIEWED_PRODUCT_AGGREGATED_YEARLY_STORE_ID_STORE_STORE_ID": true, + "REPORT_VIEWED_PRD_AGGRED_YEARLY_PRD_ID_CAT_PRD_ENTT_ENTT_ID": true, + "REPORT_VIEWED_PRD_AGGRED_YEARLY_PERIOD_STORE_ID_PRD_ID": true, + "REPORT_VIEWED_PRD_AGGRED_YEARLY_PRD_ID_SEQUENCE_PRD_SEQUENCE_VAL": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/RequireJs/Test/Mftf/composer.json b/app/code/Magento/RequireJs/Test/Mftf/composer.json deleted file mode 100644 index 57bd4809b4140..0000000000000 --- a/app/code/Magento/RequireJs/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-require-js", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Review/Block/Adminhtml/Add/Form.php b/app/code/Magento/Review/Block/Adminhtml/Add/Form.php index 2d619725ae201..04e6343eb43ca 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add/Form.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add/Form.php @@ -142,11 +142,6 @@ protected function _prepareForm() $fieldset->addField('product_id', 'hidden', ['name' => 'product_id']); - /*$gridFieldset = $form->addFieldset('add_review_grid', array('legend' => __('Please select a product'))); - $gridFieldset->addField('products_grid', 'note', array( - 'text' => $this->getLayout()->createBlock(\Magento\Review\Block\Adminhtml\Product\Grid::class)->toHtml(), - ));*/ - $form->setMethod('post'); $form->setUseContainer(true); $form->setId('edit_form'); diff --git a/app/code/Magento/Review/Block/Adminhtml/Rating/Edit/Tab/Form.php b/app/code/Magento/Review/Block/Adminhtml/Rating/Edit/Tab/Form.php index 0841388252905..dbf0a79bc42ff 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Rating/Edit/Tab/Form.php +++ b/app/code/Magento/Review/Block/Adminhtml/Rating/Edit/Tab/Form.php @@ -17,7 +17,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic /** * @var string */ - protected $_template = 'rating/form.phtml'; + protected $_template = 'Magento_Review::rating/form.phtml'; /** * Session diff --git a/app/code/Magento/Review/Block/Adminhtml/Rss/Grid/Link.php b/app/code/Magento/Review/Block/Adminhtml/Rss/Grid/Link.php index 5d2ec9fc186ca..def0e896fc95f 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Rss/Grid/Link.php +++ b/app/code/Magento/Review/Block/Adminhtml/Rss/Grid/Link.php @@ -16,7 +16,7 @@ class Link extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'rss/grid/link.phtml'; + protected $_template = 'Magento_Review::rss/grid/link.phtml'; /** * @var \Magento\Framework\App\Rss\UrlBuilderInterface diff --git a/app/code/Magento/Review/Block/Customer/ListCustomer.php b/app/code/Magento/Review/Block/Customer/ListCustomer.php index 8377cc73646b9..eb67af5780ddb 100644 --- a/app/code/Magento/Review/Block/Customer/ListCustomer.php +++ b/app/code/Magento/Review/Block/Customer/ListCustomer.php @@ -154,13 +154,13 @@ public function getProductLink() /** * Get product URL * - * @param \Magento\Review\Model\Review $review + * @param \Magento\Catalog\Model\Product $product * @return string * @since 100.2.0 */ - public function getProductUrl($review) + public function getProductUrl($product) { - return $this->getUrl('catalog/product/view', ['id' => $review->getEntityPkValue()]); + return $product->getProductUrl(); } /** diff --git a/app/code/Magento/Review/Block/Customer/Recent.php b/app/code/Magento/Review/Block/Customer/Recent.php index 8f593f5695812..5c7f1ec2c0dad 100644 --- a/app/code/Magento/Review/Block/Customer/Recent.php +++ b/app/code/Magento/Review/Block/Customer/Recent.php @@ -20,7 +20,7 @@ class Recent extends \Magento\Framework\View\Element\Template * * @var string */ - protected $_template = 'customer/list.phtml'; + protected $_template = 'Magento_Review::customer/list.phtml'; /** * Product reviews collection diff --git a/app/code/Magento/Review/Block/Customer/View.php b/app/code/Magento/Review/Block/Customer/View.php index b7dfd4b969a9d..237b972f16573 100644 --- a/app/code/Magento/Review/Block/Customer/View.php +++ b/app/code/Magento/Review/Block/Customer/View.php @@ -23,7 +23,7 @@ class View extends \Magento\Catalog\Block\Product\AbstractProduct * * @var string */ - protected $_template = 'customer/view.phtml'; + protected $_template = 'Magento_Review::customer/view.phtml'; /** * Catalog product model diff --git a/app/code/Magento/Review/Block/Form.php b/app/code/Magento/Review/Block/Form.php index 440e13deb5839..c6dfad8265ac7 100644 --- a/app/code/Magento/Review/Block/Form.php +++ b/app/code/Magento/Review/Block/Form.php @@ -139,7 +139,7 @@ protected function _construct() ); } - $this->setTemplate('form.phtml'); + $this->setTemplate('Magento_Review::form.phtml'); } /** diff --git a/app/code/Magento/Review/Block/Rating/Entity/Detailed.php b/app/code/Magento/Review/Block/Rating/Entity/Detailed.php index de871d9061428..eee00483ace66 100644 --- a/app/code/Magento/Review/Block/Rating/Entity/Detailed.php +++ b/app/code/Magento/Review/Block/Rating/Entity/Detailed.php @@ -15,7 +15,7 @@ class Detailed extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'detailed.phtml'; + protected $_template = 'Magento_Review::detailed.phtml'; /** * @var \Magento\Review\Model\RatingFactory @@ -49,7 +49,7 @@ protected function _toHtml() $reviewsCount = $this->_ratingFactory->create()->getTotalReviews($entityId, true); if ($reviewsCount == 0) { #return __('Be the first to review this product'); - $this->setTemplate('empty.phtml'); + $this->setTemplate('Magento_Review::empty.phtml'); return parent::_toHtml(); } diff --git a/app/code/Magento/Review/Block/View.php b/app/code/Magento/Review/Block/View.php index e2d0355671688..95b7176b48c44 100644 --- a/app/code/Magento/Review/Block/View.php +++ b/app/code/Magento/Review/Block/View.php @@ -19,7 +19,7 @@ class View extends \Magento\Catalog\Block\Product\AbstractProduct * * @var string */ - protected $_template = 'view.phtml'; + protected $_template = 'Magento_Review::view.phtml'; /** * Rating option model diff --git a/app/code/Magento/Review/Controller/Product/ListAjax.php b/app/code/Magento/Review/Controller/Product/ListAjax.php index 0180e5a7ee035..a309b5f0626a4 100644 --- a/app/code/Magento/Review/Controller/Product/ListAjax.php +++ b/app/code/Magento/Review/Controller/Product/ListAjax.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Review\Controller\Product; -use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\View\Result\Layout; use Magento\Review\Controller\Product as ProductController; use Magento\Framework\Controller\ResultFactory; @@ -14,17 +17,16 @@ class ListAjax extends ProductController /** * Show list of product's reviews * - * @return \Magento\Framework\View\Result\Layout + * @return ResponseInterface|ResultInterface|Layout */ public function execute() { if (!$this->initProduct()) { - throw new LocalizedException(__("The product can't be initialized.")); - } else { - /** @var \Magento\Framework\View\Result\Layout $resultLayout */ - $resultLayout = $this->resultFactory->create(ResultFactory::TYPE_LAYOUT); + /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ + $resultForward = $this->resultFactory->create(ResultFactory::TYPE_FORWARD); + return $resultForward->forward('noroute'); } - return $resultLayout; + return $this->resultFactory->create(ResultFactory::TYPE_LAYOUT); } } diff --git a/app/code/Magento/Review/Model/ResourceModel/Rating/Collection.php b/app/code/Magento/Review/Model/ResourceModel/Rating/Collection.php index cbbe17a47c0ad..0dcb9da6a8c75 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Rating/Collection.php +++ b/app/code/Magento/Review/Model/ResourceModel/Rating/Collection.php @@ -141,7 +141,6 @@ public function setStoreFilter($storeId) 'main_table.rating_id = store.rating_id', [] ); - // ->group('main_table.rating_id') $this->_isStoreJoined = true; } $inCondition = $connection->prepareSqlCondition('store.store_id', ['in' => $storeId]); diff --git a/app/code/Magento/Review/Test/Mftf/composer.json b/app/code/Magento/Review/Test/Mftf/composer.json deleted file mode 100644 index ae12d545ec244..0000000000000 --- a/app/code/Magento/Review/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-review", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-newsletter": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-review-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Review/Test/Unit/Observer/ProcessProductAfterDeleteEventObserverTest.php b/app/code/Magento/Review/Test/Unit/Observer/ProcessProductAfterDeleteEventObserverTest.php new file mode 100644 index 0000000000000..9d3980a99f9a4 --- /dev/null +++ b/app/code/Magento/Review/Test/Unit/Observer/ProcessProductAfterDeleteEventObserverTest.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Review\Test\Unit\Observer; + +use Magento\Catalog\Model\Product; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Review\Model\ResourceModel\Rating; +use Magento\Review\Model\ResourceModel\Review; +use Magento\Review\Observer\ProcessProductAfterDeleteEventObserver; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * Class ProcessProductAfterDeleteEventObserverTest + */ +class ProcessProductAfterDeleteEventObserverTest extends TestCase +{ + /** + * Testable Object + * + * @var ProcessProductAfterDeleteEventObserver + */ + private $observer; + + /** + * @var Review|PHPUnit_Framework_MockObject_MockObject + */ + private $resourceReviewMock; + + /** + * @var Rating|PHPUnit_Framework_MockObject_MockObject + */ + private $resourceRatingMock; + + /** + * Set up + */ + protected function setUp() + { + $this->resourceReviewMock = $this->createMock(Review::class); + $this->resourceRatingMock = $this->createMock(Rating::class); + + $this->observer = new ProcessProductAfterDeleteEventObserver( + $this->resourceReviewMock, + $this->resourceRatingMock + ); + } + + /** + * Test cleanup product reviews after product delete + * + * @return void + */ + public function testCleanupProductReviewsWithProduct() + { + $productId = 1; + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMock(); + + $productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMock(); + + $productMock->expects(self::exactly(3)) + ->method('getId') + ->willReturn($productId); + $eventMock->expects($this->once()) + ->method('getProduct') + ->willReturn($productMock); + $observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($eventMock); + $this->resourceReviewMock->expects($this->once()) + ->method('deleteReviewsByProductId') + ->willReturnSelf(); + $this->resourceRatingMock->expects($this->once()) + ->method('deleteAggregatedRatingsByProductId') + ->willReturnSelf(); + + $this->observer->execute($observerMock); + } + + /** + * Test with no event product + * + * @return void + */ + public function testCleanupProductReviewsWithoutProduct() + { + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMock(); + + $eventMock->expects($this->once()) + ->method('getProduct') + ->willReturn(null); + $observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($eventMock); + $this->resourceReviewMock->expects($this->never()) + ->method('deleteReviewsByProductId') + ->willReturnSelf(); + $this->resourceRatingMock->expects($this->never()) + ->method('deleteAggregatedRatingsByProductId') + ->willReturnSelf(); + + $this->observer->execute($observerMock); + } +} diff --git a/app/code/Magento/Review/etc/db_schema_whitelist.json b/app/code/Magento/Review/etc/db_schema_whitelist.json index 93ffab116b4da..b7781570c4a3d 100644 --- a/app/code/Magento/Review/etc/db_schema_whitelist.json +++ b/app/code/Magento/Review/etc/db_schema_whitelist.json @@ -1,207 +1,207 @@ { - "review_entity": { - "column": { - "entity_id": true, - "entity_code": true - }, - "constraint": { - "PRIMARY": true - } - }, - "review_status": { - "column": { - "status_id": true, - "status_code": true - }, - "constraint": { - "PRIMARY": true - } - }, - "review": { - "column": { - "review_id": true, - "created_at": true, - "entity_id": true, - "entity_pk_value": true, - "status_id": true - }, - "index": { - "REVIEW_ENTITY_ID": true, - "REVIEW_STATUS_ID": true, - "REVIEW_ENTITY_PK_VALUE": true - }, - "constraint": { - "PRIMARY": true, - "REVIEW_ENTITY_ID_REVIEW_ENTITY_ENTITY_ID": true, - "REVIEW_STATUS_ID_REVIEW_STATUS_STATUS_ID": true - } - }, - "review_detail": { - "column": { - "detail_id": true, - "review_id": true, - "store_id": true, - "title": true, - "detail": true, - "nickname": true, - "customer_id": true - }, - "index": { - "REVIEW_DETAIL_REVIEW_ID": true, - "REVIEW_DETAIL_STORE_ID": true, - "REVIEW_DETAIL_CUSTOMER_ID": true - }, - "constraint": { - "PRIMARY": true, - "REVIEW_DETAIL_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "REVIEW_DETAIL_REVIEW_ID_REVIEW_REVIEW_ID": true, - "REVIEW_DETAIL_STORE_ID_STORE_STORE_ID": true - } - }, - "review_entity_summary": { - "column": { - "primary_id": true, - "entity_pk_value": true, - "entity_type": true, - "reviews_count": true, - "rating_summary": true, - "store_id": true - }, - "index": { - "REVIEW_ENTITY_SUMMARY_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "REVIEW_ENTITY_SUMMARY_STORE_ID_STORE_STORE_ID": true - } - }, - "review_store": { - "column": { - "review_id": true, - "store_id": true - }, - "index": { - "REVIEW_STORE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "REVIEW_STORE_REVIEW_ID_REVIEW_REVIEW_ID": true, - "REVIEW_STORE_STORE_ID_STORE_STORE_ID": true - } - }, - "rating_entity": { - "column": { - "entity_id": true, - "entity_code": true - }, - "constraint": { - "PRIMARY": true, - "RATING_ENTITY_ENTITY_CODE": true - } - }, - "rating": { - "column": { - "rating_id": true, - "entity_id": true, - "rating_code": true, - "position": true, - "is_active": true - }, - "index": { - "RATING_ENTITY_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_ENTITY_ID_RATING_ENTITY_ENTITY_ID": true, - "RATING_RATING_CODE": true - } - }, - "rating_option": { - "column": { - "option_id": true, - "rating_id": true, - "code": true, - "value": true, - "position": true - }, - "index": { - "RATING_OPTION_RATING_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_OPTION_RATING_ID_RATING_RATING_ID": true - } - }, - "rating_option_vote": { - "column": { - "vote_id": true, - "option_id": true, - "remote_ip": true, - "remote_ip_long": true, - "customer_id": true, - "entity_pk_value": true, - "rating_id": true, - "review_id": true, - "percent": true, - "value": true - }, - "index": { - "RATING_OPTION_VOTE_OPTION_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_OPTION_VOTE_OPTION_ID_RATING_OPTION_OPTION_ID": true, - "RATING_OPTION_VOTE_REVIEW_ID_REVIEW_REVIEW_ID": true - } - }, - "rating_option_vote_aggregated": { - "column": { - "primary_id": true, - "rating_id": true, - "entity_pk_value": true, - "vote_count": true, - "vote_value_sum": true, - "percent": true, - "percent_approved": true, - "store_id": true - }, - "index": { - "RATING_OPTION_VOTE_AGGREGATED_RATING_ID": true, - "RATING_OPTION_VOTE_AGGREGATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_OPTION_VOTE_AGGREGATED_RATING_ID_RATING_RATING_ID": true, - "RATING_OPTION_VOTE_AGGREGATED_STORE_ID_STORE_STORE_ID": true - } - }, - "rating_store": { - "column": { - "rating_id": true, - "store_id": true - }, - "index": { - "RATING_STORE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_STORE_STORE_ID_STORE_STORE_ID": true, - "RATING_STORE_RATING_ID_RATING_RATING_ID": true - } - }, - "rating_title": { - "column": { - "rating_id": true, - "store_id": true, - "value": true - }, - "index": { - "RATING_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "RATING_TITLE_RATING_ID_RATING_RATING_ID": true, - "RATING_TITLE_STORE_ID_STORE_STORE_ID": true + "review_entity": { + "column": { + "entity_id": true, + "entity_code": true + }, + "constraint": { + "PRIMARY": true + } + }, + "review_status": { + "column": { + "status_id": true, + "status_code": true + }, + "constraint": { + "PRIMARY": true + } + }, + "review": { + "column": { + "review_id": true, + "created_at": true, + "entity_id": true, + "entity_pk_value": true, + "status_id": true + }, + "index": { + "REVIEW_ENTITY_ID": true, + "REVIEW_STATUS_ID": true, + "REVIEW_ENTITY_PK_VALUE": true + }, + "constraint": { + "PRIMARY": true, + "REVIEW_ENTITY_ID_REVIEW_ENTITY_ENTITY_ID": true, + "REVIEW_STATUS_ID_REVIEW_STATUS_STATUS_ID": true + } + }, + "review_detail": { + "column": { + "detail_id": true, + "review_id": true, + "store_id": true, + "title": true, + "detail": true, + "nickname": true, + "customer_id": true + }, + "index": { + "REVIEW_DETAIL_REVIEW_ID": true, + "REVIEW_DETAIL_STORE_ID": true, + "REVIEW_DETAIL_CUSTOMER_ID": true + }, + "constraint": { + "PRIMARY": true, + "REVIEW_DETAIL_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "REVIEW_DETAIL_REVIEW_ID_REVIEW_REVIEW_ID": true, + "REVIEW_DETAIL_STORE_ID_STORE_STORE_ID": true + } + }, + "review_entity_summary": { + "column": { + "primary_id": true, + "entity_pk_value": true, + "entity_type": true, + "reviews_count": true, + "rating_summary": true, + "store_id": true + }, + "index": { + "REVIEW_ENTITY_SUMMARY_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "REVIEW_ENTITY_SUMMARY_STORE_ID_STORE_STORE_ID": true + } + }, + "review_store": { + "column": { + "review_id": true, + "store_id": true + }, + "index": { + "REVIEW_STORE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "REVIEW_STORE_REVIEW_ID_REVIEW_REVIEW_ID": true, + "REVIEW_STORE_STORE_ID_STORE_STORE_ID": true + } + }, + "rating_entity": { + "column": { + "entity_id": true, + "entity_code": true + }, + "constraint": { + "PRIMARY": true, + "RATING_ENTITY_ENTITY_CODE": true + } + }, + "rating": { + "column": { + "rating_id": true, + "entity_id": true, + "rating_code": true, + "position": true, + "is_active": true + }, + "index": { + "RATING_ENTITY_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_ENTITY_ID_RATING_ENTITY_ENTITY_ID": true, + "RATING_RATING_CODE": true + } + }, + "rating_option": { + "column": { + "option_id": true, + "rating_id": true, + "code": true, + "value": true, + "position": true + }, + "index": { + "RATING_OPTION_RATING_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_OPTION_RATING_ID_RATING_RATING_ID": true + } + }, + "rating_option_vote": { + "column": { + "vote_id": true, + "option_id": true, + "remote_ip": true, + "remote_ip_long": true, + "customer_id": true, + "entity_pk_value": true, + "rating_id": true, + "review_id": true, + "percent": true, + "value": true + }, + "index": { + "RATING_OPTION_VOTE_OPTION_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_OPTION_VOTE_OPTION_ID_RATING_OPTION_OPTION_ID": true, + "RATING_OPTION_VOTE_REVIEW_ID_REVIEW_REVIEW_ID": true + } + }, + "rating_option_vote_aggregated": { + "column": { + "primary_id": true, + "rating_id": true, + "entity_pk_value": true, + "vote_count": true, + "vote_value_sum": true, + "percent": true, + "percent_approved": true, + "store_id": true + }, + "index": { + "RATING_OPTION_VOTE_AGGREGATED_RATING_ID": true, + "RATING_OPTION_VOTE_AGGREGATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_OPTION_VOTE_AGGREGATED_RATING_ID_RATING_RATING_ID": true, + "RATING_OPTION_VOTE_AGGREGATED_STORE_ID_STORE_STORE_ID": true + } + }, + "rating_store": { + "column": { + "rating_id": true, + "store_id": true + }, + "index": { + "RATING_STORE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_STORE_STORE_ID_STORE_STORE_ID": true, + "RATING_STORE_RATING_ID_RATING_RATING_ID": true + } + }, + "rating_title": { + "column": { + "rating_id": true, + "store_id": true, + "value": true + }, + "index": { + "RATING_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "RATING_TITLE_RATING_ID_RATING_RATING_ID": true, + "RATING_TITLE_STORE_ID_STORE_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Review/view/frontend/templates/customer/list.phtml b/app/code/Magento/Review/view/frontend/templates/customer/list.phtml index 725df702bb465..a65ec01d626eb 100644 --- a/app/code/Magento/Review/view/frontend/templates/customer/list.phtml +++ b/app/code/Magento/Review/view/frontend/templates/customer/list.phtml @@ -22,29 +22,29 @@ </tr> </thead> <tbody> - <?php foreach ($block->getReviews() as $_review): ?> + <?php foreach ($block->getReviews() as $review): ?> <tr> - <td data-th="<?= $block->escapeHtml(__('Created')) ?>" class="col date"><?= $block->escapeHtml($block->dateFormat($_review->getReviewCreatedAt())) ?></td> + <td data-th="<?= $block->escapeHtml(__('Created')) ?>" class="col date"><?= $block->escapeHtml($block->dateFormat($review->getReviewCreatedAt())) ?></td> <td data-th="<?= $block->escapeHtml(__('Product Name')) ?>" class="col item"> <strong class="product-name"> - <a href="<?= $block->escapeUrl($block->getProductUrl($_review)) ?>"><?= $block->escapeHtml($_review->getName()) ?></a> + <a href="<?= $block->escapeUrl($block->getProductUrl($review)) ?>"><?= $block->escapeHtml($review->getName()) ?></a> </strong> </td> <td data-th="<?= $block->escapeHtml(__('Rating')) ?>" class="col summary"> - <?php if ($_review->getSum()): ?> + <?php if ($review->getSum()): ?> <div class="rating-summary"> <span class="label"><span><?= $block->escapeHtml(__('Rating')) ?>:</span></span> - <div class="rating-result" title="<?= /* @noEscape */ ((int)$_review->getSum() / (int)$_review->getCount()) ?>%"> - <span style="width:<?= /* @noEscape */ ((int)$_review->getSum() / (int)$_review->getCount()) ?>%;"><span><?= /* @noEscape */ ((int)$_review->getSum() / (int)$_review->getCount()) ?>%</span></span> + <div class="rating-result" title="<?= /* @noEscape */ ((int)$review->getSum() / (int)$review->getCount()) ?>%"> + <span style="width:<?= /* @noEscape */ ((int)$review->getSum() / (int)$review->getCount()) ?>%;"><span><?= /* @noEscape */ ((int)$review->getSum() / (int)$review->getCount()) ?>%</span></span> </div> </div> <?php endif; ?> </td> <td data-th="<?= $block->escapeHtmlAttr(__('Review')) ?>" class="col description"> - <?= $this->helper('Magento\Review\Helper\Data')->getDetailHtml($_review->getDetail()) ?> + <?= $this->helper('Magento\Review\Helper\Data')->getDetailHtml($review->getDetail()) ?> </td> <td data-th="<?= $block->escapeHtmlAttr(__('Actions')) ?>" class="col actions"> - <a href="<?= $block->escapeUrl($block->getReviewUrl($_review)) ?>" class="action more"> + <a href="<?= $block->escapeUrl($block->getReviewUrl($review)) ?>" class="action more"> <span><?= $block->escapeHtml(__('See Details')) ?></span> </a> </td> diff --git a/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json b/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 0164913753cb4..0000000000000 --- a/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-review-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-review": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Robots/Test/Mftf/composer.json b/app/code/Magento/Robots/Test/Mftf/composer.json deleted file mode 100644 index d3f2447e7970a..0000000000000 --- a/app/code/Magento/Robots/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-robots", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Rss/Block/Feeds.php b/app/code/Magento/Rss/Block/Feeds.php index 2e88d25c02891..86998f87f5c17 100644 --- a/app/code/Magento/Rss/Block/Feeds.php +++ b/app/code/Magento/Rss/Block/Feeds.php @@ -16,7 +16,7 @@ class Feeds extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'feeds.phtml'; + protected $_template = 'Magento_Rss::feeds.phtml'; /** * @var \Magento\Framework\App\Rss\RssManagerInterface diff --git a/app/code/Magento/Rss/Test/Mftf/composer.json b/app/code/Magento/Rss/Test/Mftf/composer.json deleted file mode 100644 index cf3a9dae8a054..0000000000000 --- a/app/code/Magento/Rss/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-rss", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index e3bec2d9959b1..a1987f67e47f2 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -355,10 +355,10 @@ public function getValueParsed() { if (!$this->hasValueParsed()) { $value = $this->getData('value'); - if (is_array($value) && isset($value[0]) && is_string($value[0])) { - $value = $value[0]; + if (is_array($value) && count($value) === 1) { + $value = reset($value); } - if ($this->isArrayOperatorType() && $value) { + if (!is_array($value) && $this->isArrayOperatorType() && $value) { $value = preg_split('#\s*[,;]\s*#', $value, null, PREG_SPLIT_NO_EMPTY); } $this->setValueParsed($value); @@ -380,7 +380,7 @@ public function isArrayOperatorType() } /** - * @return array + * @return mixed */ public function getValue() { diff --git a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php index e1c9bf99f2675..2894de0f19b87 100644 --- a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php +++ b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php @@ -163,8 +163,22 @@ protected function _getMappedSqlCondition( $this->_conditionOperatorMap[$conditionOperator] ); + $bindValue = $condition->getBindArgumentValue(); + $expression = $value . $this->_connection->quoteInto($sql, $bindValue); + + // values for multiselect attributes can be saved in comma-separated format + // below is a solution for matching such conditions with selected values + if (is_array($bindValue) && \in_array($conditionOperator, ['()', '{}'], true)) { + foreach ($bindValue as $item) { + $expression .= $this->_connection->quoteInto( + " OR (FIND_IN_SET (?, {$this->_connection->quoteIdentifier($argument)}) > 0)", + $item + ); + } + } + return $this->_expressionFactory->create( - ['expression' => $value . $this->_connection->quoteInto($sql, $condition->getBindArgumentValue())] + ['expression' => $expression] ); } @@ -174,6 +188,7 @@ protected function _getMappedSqlCondition( * @param bool $isDefaultStoreUsed * @return string * @SuppressWarnings(PHPMD.NPathComplexity) + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getMappedSqlCombination( Combine $combine, diff --git a/app/code/Magento/Rule/Test/Mftf/composer.json b/app/code/Magento/Rule/Test/Mftf/composer.json deleted file mode 100644 index 827d8603fdb99..0000000000000 --- a/app/code/Magento/Rule/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Items/Column/Name.php b/app/code/Magento/Sales/Block/Adminhtml/Items/Column/Name.php index 1ed9d59d29a72..87c15e474d11f 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Items/Column/Name.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Items/Column/Name.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Block\Adminhtml\Items\Column; +use Magento\Framework\Filter\TruncateFilter\Result; + /** * Sales Order items name column renderer * @@ -13,6 +15,11 @@ */ class Name extends \Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn { + /** + * @var Result + */ + private $truncateResult = null; + /** * Truncate string * @@ -22,13 +29,15 @@ class Name extends \Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn * @param string &$remainder * @param bool $breakWords * @return string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function truncateString($value, $length = 80, $etc = '...', &$remainder = '', $breakWords = true) { - return $this->filterManager->truncate( + $this->truncateResult = $this->filterManager->truncateFilter( $value, - ['length' => $length, 'etc' => $etc, 'remainder' => $remainder, 'breakWords' => $breakWords] + ['length' => $length, 'etc' => $etc, 'breakWords' => $breakWords] ); + return $this->truncateResult->getValue(); } /** @@ -40,8 +49,11 @@ public function truncateString($value, $length = 80, $etc = '...', &$remainder = public function getFormattedOption($value) { $remainder = ''; - $value = $this->truncateString($value, 55, '', $remainder); - $result = ['value' => nl2br($value), 'remainder' => nl2br($remainder)]; + $this->truncateString($value, 55, '', $remainder); + $result = [ + 'value' => nl2br($this->truncateResult->getValue()), + 'remainder' => nl2br($this->truncateResult->getRemainder()) + ]; return $result; } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Address/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Address/Form.php index f2b454260dc22..6cab109b44dbb 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Address/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Address/Form.php @@ -19,7 +19,7 @@ class Form extends \Magento\Sales\Block\Adminhtml\Order\Create\Form\Address * * @var string */ - protected $_template = 'order/address/form.phtml'; + protected $_template = 'Magento_Sales::order/address/form.phtml'; /** * Core registry diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form.php index 37e8d8e784744..12a8270acfff0 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form.php @@ -189,6 +189,7 @@ public function getOrderDataJson() $data['shipping_method_reseted'] = !(bool)$this->getQuote()->getShippingAddress()->getShippingMethod(); $data['payment_method'] = $this->getQuote()->getPayment()->getMethod(); } + $data['quote_id'] = $this->_sessionQuote->getQuoteId(); return $this->_jsonEncoder->encode($data); } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 3623e72341a22..26379a05fe694 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -132,7 +132,15 @@ protected function _prepareForm() $this->_addAttributesToForm($attributes, $fieldset); $this->_form->addFieldNameSuffix('order[account]'); - $this->_form->setValues($this->getFormValues()); + + $formValues = $this->getFormValues(); + foreach ($attributes as $code => $attribute) { + $defaultValue = $attribute->getDefaultValue(); + if (isset($defaultValue) && !isset($formValues[$code])) { + $formValues[$code] = $defaultValue; + } + } + $this->_form->setValues($formValues); return $this; } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Grandtotal.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Grandtotal.php index eb437915ad668..cf9f8a44dee27 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Grandtotal.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Grandtotal.php @@ -20,7 +20,7 @@ class Grandtotal extends \Magento\Sales\Block\Adminhtml\Order\Create\Totals\Defa * * @var string */ - protected $_template = 'order/create/totals/grandtotal.phtml'; + protected $_template = 'Magento_Sales::order/create/totals/grandtotal.phtml'; /** * Tax config diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Shipping.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Shipping.php index 9225d8c2e5f68..34a9ed8070e26 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Shipping.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Shipping.php @@ -20,7 +20,7 @@ class Shipping extends \Magento\Sales\Block\Adminhtml\Order\Create\Totals\Defaul * * @var string */ - protected $_template = 'order/create/totals/shipping.phtml'; + protected $_template = 'Magento_Sales::order/create/totals/shipping.phtml'; /** * Tax config diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Subtotal.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Subtotal.php index cfdd73de9d8b8..166f3c9637ebb 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Subtotal.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Subtotal.php @@ -20,7 +20,7 @@ class Subtotal extends \Magento\Sales\Block\Adminhtml\Order\Create\Totals\Defaul * * @var string */ - protected $_template = 'order/create/totals/subtotal.phtml'; + protected $_template = 'Magento_Sales::order/create/totals/subtotal.phtml'; /** * Tax config diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Tax.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Tax.php index d3da37c3f1bf8..207a4eca60213 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Tax.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Totals/Tax.php @@ -18,5 +18,5 @@ class Tax extends \Magento\Sales\Block\Adminhtml\Order\Create\Totals\DefaultTota * * @var string */ - protected $_template = 'order/create/totals/tax.phtml'; + protected $_template = 'Magento_Sales::order/create/totals/tax.phtml'; } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Details.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Details.php index 5c3a7fce805cc..261f4b0cfd12a 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Details.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Details.php @@ -14,5 +14,5 @@ class Details extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'order/details.phtml'; + protected $_template = 'Magento_Sales::order/details.phtml'; } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Totalbar.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Totalbar.php index 648a281228908..c4ce48d162c2c 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Totalbar.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Totalbar.php @@ -8,6 +8,7 @@ /** * Adminhtml creditmemo bar * + * @deprecated * @api * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Form.php index 82c3effcab62d..6c06e9d624c81 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Form.php @@ -17,5 +17,5 @@ class Form extends \Magento\Backend\Block\Template * * @var string */ - protected $_template = 'order/view/form.phtml'; + protected $_template = 'Magento_Sales::order/view/form.phtml'; } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php index 5489a0b2e513f..64b53d10d4af6 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php @@ -18,7 +18,7 @@ class History extends \Magento\Backend\Block\Template implements \Magento\Backen * * @var string */ - protected $_template = 'order/view/tab/history.phtml'; + protected $_template = 'Magento_Sales::order/view/tab/history.phtml'; /** * Core registry diff --git a/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php b/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php index fbb78970a4de0..512539824da20 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php @@ -14,7 +14,7 @@ class Link extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'rss/order/grid/link.phtml'; + protected $_template = 'Magento_Sales::rss/order/grid/link.phtml'; /** * @var \Magento\Framework\App\Rss\UrlBuilderInterface diff --git a/app/code/Magento/Sales/Block/Order/Info/Buttons.php b/app/code/Magento/Sales/Block/Order/Info/Buttons.php index a27b55cd8543f..18e79f6a76ecf 100644 --- a/app/code/Magento/Sales/Block/Order/Info/Buttons.php +++ b/app/code/Magento/Sales/Block/Order/Info/Buttons.php @@ -20,7 +20,7 @@ class Buttons extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'order/info/buttons.phtml'; + protected $_template = 'Magento_Sales::order/info/buttons.phtml'; /** * Core registry diff --git a/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php b/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php index 77e20eaa8d07b..2b84b8f1444b6 100644 --- a/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php +++ b/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php @@ -16,7 +16,7 @@ class Rss extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'order/info/buttons/rss.phtml'; + protected $_template = 'Magento_Sales::order/info/buttons/rss.phtml'; /** * @var \Magento\Sales\Model\OrderFactory diff --git a/app/code/Magento/Sales/Block/Order/Invoice.php b/app/code/Magento/Sales/Block/Order/Invoice.php index 2d8448ea5bc98..24ddf4bac7696 100644 --- a/app/code/Magento/Sales/Block/Order/Invoice.php +++ b/app/code/Magento/Sales/Block/Order/Invoice.php @@ -18,7 +18,7 @@ class Invoice extends \Magento\Sales\Block\Order\Invoice\Items /** * @var string */ - protected $_template = 'order/invoice.phtml'; + protected $_template = 'Magento_Sales::order/invoice.phtml'; /** * @var \Magento\Framework\App\Http\Context diff --git a/app/code/Magento/Sales/Block/Order/View.php b/app/code/Magento/Sales/Block/Order/View.php index 870e2e15ab7b3..03d1340e0f690 100644 --- a/app/code/Magento/Sales/Block/Order/View.php +++ b/app/code/Magento/Sales/Block/Order/View.php @@ -18,7 +18,7 @@ class View extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'order/view.phtml'; + protected $_template = 'Magento_Sales::order/view.phtml'; /** * Core registry diff --git a/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php b/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php new file mode 100644 index 0000000000000..47ef24c542c2a --- /dev/null +++ b/app/code/Magento/Sales/Console/Command/EncryptionPaymentDataUpdateCommand.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sales\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Command\Command; +use Magento\Framework\Console\Cli; + +/** + * Command for updating encrypted credit card data to the latest cipher + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class EncryptionPaymentDataUpdateCommand extends Command +{ + /** Command name */ + const NAME = 'encryption:payment-data:update'; + + /** + * @var \Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate + */ + private $paymentResource; + + /** + * @param \Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate $paymentResource + */ + public function __construct( + \Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate $paymentResource + ) { + $this->paymentResource = $paymentResource; + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName(self::NAME) + ->setDescription( + 'Re-encrypts encrypted credit card data with latest encryption cipher.' + ); + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $this->paymentResource->reEncryptCreditCardNumbers(); + } catch (\Exception $e) { + $output->writeln('<error>' . $e->getMessage() . '</error>'); + return Cli::RETURN_FAILURE; + } + + return Cli::RETURN_SUCCESS; + } +} diff --git a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php index d7ab99377e1b5..dd4d73930cc8f 100644 --- a/app/code/Magento/Sales/Controller/AbstractController/Reorder.php +++ b/app/code/Magento/Sales/Controller/AbstractController/Reorder.php @@ -59,13 +59,16 @@ public function execute() $cart->addOrderItem($item); } catch (\Magento\Framework\Exception\LocalizedException $e) { if ($this->_objectManager->get(\Magento\Checkout\Model\Session::class)->getUseNotice(true)) { - $this->messageManager->addNotice($e->getMessage()); + $this->messageManager->addNoticeMessage($e->getMessage()); } else { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } return $resultRedirect->setPath('*/*/history'); } catch (\Exception $e) { - $this->messageManager->addException($e, __('We can\'t add this item to your shopping cart right now.')); + $this->messageManager->addExceptionMessage( + $e, + __('We can\'t add this item to your shopping cart right now.') + ); return $resultRedirect->setPath('checkout/cart'); } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Email.php b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Email.php index ac34af6f4ce6e..e3571f3cc02bc 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Email.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Email.php @@ -33,7 +33,7 @@ public function execute() $this->_objectManager->create(\Magento\Sales\Api\CreditmemoManagementInterface::class) ->notify($creditmemoId); - $this->messageManager->addSuccess(__('You sent the message.')); + $this->messageManager->addSuccessMessage(__('You sent the message.')); $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('sales/order_creditmemo/view', ['creditmemo_id' => $creditmemoId]); return $resultRedirect; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Pdfcreditmemos.php b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Pdfcreditmemos.php index 83486704984f7..94ca6a0375e7c 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Pdfcreditmemos.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/Pdfcreditmemos.php @@ -74,9 +74,12 @@ public function __construct( */ public function massAction(AbstractCollection $collection) { + $pdf = $this->pdfCreditmemo->getPdf($collection); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('creditmemo%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfCreditmemo->getPdf($collection)->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/PrintAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/PrintAction.php index d6c94feb57606..c5902aac33355 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/PrintAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/PrintAction.php @@ -53,6 +53,7 @@ public function __construct( /** * @return ResponseInterface|\Magento\Backend\Model\View\Result\Forward + * @throws \Exception */ public function execute() { @@ -69,9 +70,11 @@ public function execute() $date = $this->_objectManager->get( \Magento\Framework\Stdlib\DateTime\DateTime::class )->date('Y-m-d_H-i-s'); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->_fileFactory->create( \creditmemo::class . $date . '.pdf', - $pdf->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Email.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Email.php index e491dd78db617..c645df152ba8f 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Email.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Email.php @@ -57,7 +57,7 @@ public function execute() \Magento\Sales\Api\InvoiceManagementInterface::class )->notify($invoice->getEntityId()); - $this->messageManager->addSuccess(__('You sent the message.')); + $this->messageManager->addSuccessMessage(__('You sent the message.')); return $this->resultRedirectFactory->create()->setPath( 'sales/invoice/view', ['order_id' => $invoice->getOrder()->getId(), 'invoice_id' => $invoiceId] diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Pdfinvoices.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Pdfinvoices.php index a949ceca53961..18cf397c0e3b9 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Pdfinvoices.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/Pdfinvoices.php @@ -75,9 +75,12 @@ public function __construct( */ public function massAction(AbstractCollection $collection) { + $pdf = $this->pdfInvoice->getPdf($collection); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('invoice%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfInvoice->getPdf($collection)->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/PrintAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/PrintAction.php index 2421267aa753d..2caa8ec2dcb83 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/PrintAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/PrintAction.php @@ -45,6 +45,7 @@ public function __construct( /** * @return ResponseInterface|void + * @throws \Exception */ public function execute() { @@ -58,9 +59,11 @@ public function execute() $date = $this->_objectManager->get( \Magento\Framework\Stdlib\DateTime\DateTime::class )->date('Y-m-d_H-i-s'); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->_fileFactory->create( 'invoice' . $date . '.pdf', - $pdf->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/View.php b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/View.php index 3615f3033ca81..300b7ee37f2ef 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/View.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Invoice/AbstractInvoice/View.php @@ -79,7 +79,7 @@ protected function getInvoice() ->get($this->getRequest()->getParam('invoice_id')); $this->registry->register('current_invoice', $invoice); } catch (\Exception $e) { - $this->messageManager->addError(__('Invoice capturing error')); + $this->messageManager->addErrorMessage(__('Invoice capturing error')); return false; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order.php b/app/code/Magento/Sales/Controller/Adminhtml/Order.php index c2299374c798c..0066c5f4c828a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order.php @@ -154,11 +154,11 @@ protected function _initOrder() try { $order = $this->orderRepository->get($id); } catch (NoSuchEntityException $e) { - $this->messageManager->addError(__('This order no longer exists.')); + $this->messageManager->addErrorMessage(__('This order no longer exists.')); $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); return false; } catch (InputException $e) { - $this->messageManager->addError(__('This order no longer exists.')); + $this->messageManager->addErrorMessage(__('This order no longer exists.')); $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); return false; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/AbstractMassAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/AbstractMassAction.php index 891bfeefc9f52..c6b45f282debc 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/AbstractMassAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/AbstractMassAction.php @@ -61,7 +61,7 @@ public function execute() $collection = $this->filter->getCollection($this->collectionFactory->create()); return $this->massAction($collection); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); return $resultRedirect->setPath($this->redirectUrl); @@ -70,7 +70,7 @@ public function execute() /** * Return component referrer url - * TODO: Technical dept referrer url should be implement as a part of Action configuration in in appropriate way + * TODO: Technical dept referrer url should be implement as a part of Action configuration in appropriate way * * @return null|string */ diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php index dde2192c3b82c..c71de8cb0252d 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/AddressSave.php @@ -114,12 +114,12 @@ public function execute() 'order_id' => $address->getParentId() ] ); - $this->messageManager->addSuccess(__('You updated the order address.')); + $this->messageManager->addSuccessMessage(__('You updated the order address.')); return $resultRedirect->setPath('sales/*/view', ['order_id' => $address->getParentId()]); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('We can\'t update the order address right now.')); + $this->messageManager->addExceptionMessage($e, __('We can\'t update the order address right now.')); } return $resultRedirect->setPath('sales/*/address', ['address_id' => $address->getId()]); } else { diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php index de41c3c737968..7e41c7417b38d 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Cancel.php @@ -24,18 +24,18 @@ public function execute() { $resultRedirect = $this->resultRedirectFactory->create(); if (!$this->isValidPostRequest()) { - $this->messageManager->addError(__('You have not canceled the item.')); + $this->messageManager->addErrorMessage(__('You have not canceled the item.')); return $resultRedirect->setPath('sales/*/'); } $order = $this->_initOrder(); if ($order) { try { $this->orderManagement->cancel($order->getEntityId()); - $this->messageManager->addSuccess(__('You canceled the order.')); + $this->messageManager->addSuccessMessage(__('You canceled the order.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('You have not canceled the item.')); + $this->messageManager->addErrorMessage(__('You have not canceled the item.')); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); } return $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getId()]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create.php index a7b41d0a780f3..dcf5e617d055c 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create.php @@ -317,7 +317,7 @@ protected function _processActionData($action = null) } } if (!$isApplyDiscount) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __( '"%1" coupon code was not applied. Do not apply discount is selected for item(s)', $this->escaper->escapeHtml($couponCode) @@ -325,14 +325,14 @@ protected function _processActionData($action = null) ); } else { if ($this->_getQuote()->getCouponCode() !== $couponCode) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __( 'The "%1" coupon code isn\'t valid. Verify the code and try again.', $this->escaper->escapeHtml($couponCode) ) ); } else { - $this->messageManager->addSuccess(__('The coupon code has been accepted.')); + $this->messageManager->addSuccessMessage(__('The coupon code has been accepted.')); } } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php index 6a9d0a5dcb8ed..bc4eb3cfba423 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/LoadBlock.php @@ -55,10 +55,10 @@ public function execute() $this->_initSession()->_processData(); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->_reloadQuote(); - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->_reloadQuote(); - $this->messageManager->addException($e, $e->getMessage()); + $this->messageManager->addExceptionMessage($e, $e->getMessage()); } $asJson = $request->getParam('json'); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php index 621705c7937cb..929e23075e070 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Save.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Sales\Controller\Adminhtml\Order\Create; use Magento\Framework\Exception\PaymentException; @@ -12,14 +13,15 @@ class Save extends \Magento\Sales\Controller\Adminhtml\Order\Create /** * Saving quote and create order * - * @return \Magento\Backend\Model\View\Result\Forward|\Magento\Backend\Model\View\Result\Redirect + * @return \Magento\Framework\Controller\ResultInterface * * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function execute() { - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultRedirectFactory->create(); + $path = 'sales/*/'; + $pathParams = []; + try { // check if the creation of a new customer is allowed if (!$this->_authorization->isAllowed('Magento_Customer::manage') @@ -49,31 +51,30 @@ public function execute() ->createOrder(); $this->_getSession()->clearStorage(); - $this->messageManager->addSuccess(__('You created the order.')); + $this->messageManager->addSuccessMessage(__('You created the order.')); if ($this->_authorization->isAllowed('Magento_Sales::actions_view')) { - $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getId()]); + $pathParams = ['order_id' => $order->getId()]; + $path = 'sales/order/view'; } else { - $resultRedirect->setPath('sales/order/index'); + $path = 'sales/order/index'; } } catch (PaymentException $e) { $this->_getOrderCreateModel()->saveQuote(); $message = $e->getMessage(); if (!empty($message)) { - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } - $resultRedirect->setPath('sales/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { // customer can be created before place order flow is completed and should be stored in current session - $this->_getSession()->setCustomerId($this->_getSession()->getQuote()->getCustomerId()); + $this->_getSession()->setCustomerId((int)$this->_getSession()->getQuote()->getCustomerId()); $message = $e->getMessage(); if (!empty($message)) { - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } - $resultRedirect->setPath('sales/*/'); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Order saving error: %1', $e->getMessage())); - $resultRedirect->setPath('sales/*/'); + $this->messageManager->addExceptionMessage($e, __('Order saving error: %1', $e->getMessage())); } - return $resultRedirect; + + return $this->resultRedirectFactory->create()->setPath($path, $pathParams); } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Cancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Cancel.php index eedf49a6cae9a..1ca0b53ee8784 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Cancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Cancel.php @@ -47,11 +47,11 @@ public function execute() \Magento\Sales\Api\CreditmemoManagementInterface::class ); $creditmemoManagement->cancel($creditmemoId); - $this->messageManager->addSuccess(__('The credit memo has been canceled.')); + $this->messageManager->addSuccessMessage(__('The credit memo has been canceled.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('Credit memo has not been canceled.')); + $this->messageManager->addErrorMessage(__('Credit memo has not been canceled.')); } $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('sales/*/view', ['creditmemo_id' => $creditmemoId]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php index 826a2a2a8b6c1..2a2445d2b2ae0 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/Save.php @@ -109,7 +109,7 @@ public function execute() $this->creditmemoSender->send($creditmemo); } - $this->messageManager->addSuccess(__('You created the credit memo.')); + $this->messageManager->addSuccessMessage(__('You created the credit memo.')); $this->_getSession()->getCommentText(true); $resultRedirect->setPath('sales/order/view', ['order_id' => $creditmemo->getOrderId()]); return $resultRedirect; @@ -119,11 +119,11 @@ public function execute() return $resultForward; } } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_getSession()->setFormData($data); } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError(__('We can\'t save the credit memo right now.')); + $this->messageManager->addErrorMessage(__('We can\'t save the credit memo right now.')); } $resultRedirect->setPath('sales/*/new', ['_current' => true]); return $resultRedirect; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/VoidAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/VoidAction.php index e7bd891fbfbff..146514265517a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/VoidAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/VoidAction.php @@ -64,11 +64,11 @@ public function execute() $transactionSave->addObject($creditmemo->getInvoice()); } $transactionSave->save(); - $this->messageManager->addSuccess(__('You voided the credit memo.')); + $this->messageManager->addSuccessMessage(__('You voided the credit memo.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t void the credit memo.')); + $this->messageManager->addErrorMessage(__('We can\'t void the credit memo.')); } $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('sales/*/view', ['creditmemo_id' => $creditmemo->getId()]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php index 057e72b6176d3..0c5864e954a4f 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/CreditmemoLoader.php @@ -138,7 +138,7 @@ protected function _canCreditmemo($order) * Check order existing */ if (!$order->getId()) { - $this->messageManager->addError(__('The order no longer exists.')); + $this->messageManager->addErrorMessage(__('The order no longer exists.')); return false; } @@ -146,7 +146,7 @@ protected function _canCreditmemo($order) * Check creditmemo create availability */ if (!$order->canCreditmemo()) { - $this->messageManager->addError(__('We can\'t create credit memo for the order.')); + $this->messageManager->addErrorMessage(__('We can\'t create credit memo for the order.')); return false; } return true; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Edit/Start.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Edit/Start.php index 14c630542dbde..b1eba5f661c2e 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Edit/Start.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Edit/Start.php @@ -36,10 +36,10 @@ public function execute() $resultRedirect->setPath('sales/order/'); } } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } catch (\Exception $e) { - $this->messageManager->addException($e, $e->getMessage()); + $this->messageManager->addExceptionMessage($e, $e->getMessage()); $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } return $resultRedirect; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Email.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Email.php index 1573a07710b65..3500de798979a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Email.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Email.php @@ -25,11 +25,11 @@ public function execute() if ($order) { try { $this->orderManagement->notify($order->getEntityId()); - $this->messageManager->addSuccess(__('You sent the order email.')); + $this->messageManager->addSuccessMessage(__('You sent the order email.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t send the email order right now.')); + $this->messageManager->addErrorMessage(__('We can\'t send the email order right now.')); $this->logger->critical($e); } return $this->resultRedirectFactory->create()->setPath( diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Hold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Hold.php index feb307ca19c31..7d2c713eece48 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Hold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Hold.php @@ -23,18 +23,18 @@ public function execute() { $resultRedirect = $this->resultRedirectFactory->create(); if (!$this->isValidPostRequest()) { - $this->messageManager->addError(__('You have not put the order on hold.')); + $this->messageManager->addErrorMessage(__('You have not put the order on hold.')); return $resultRedirect->setPath('sales/*/'); } $order = $this->_initOrder(); if ($order) { try { $this->orderManagement->hold($order->getEntityId()); - $this->messageManager->addSuccess(__('You put the order on hold.')); + $this->messageManager->addSuccessMessage(__('You put the order on hold.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('You have not put the order on hold.')); + $this->messageManager->addErrorMessage(__('You have not put the order on hold.')); } $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getId()]); return $resultRedirect; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Cancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Cancel.php index a031591b37fae..e4a74329502f1 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Cancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Cancel.php @@ -31,11 +31,11 @@ public function execute() )->addObject( $invoice->getOrder() )->save(); - $this->messageManager->addSuccess(__('You canceled the invoice.')); + $this->messageManager->addSuccessMessage(__('You canceled the invoice.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('Invoice canceling error')); + $this->messageManager->addErrorMessage(__('Invoice canceling error')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Capture.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Capture.php index 030ccc9d9d3f6..43270264ecbda 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Capture.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Capture.php @@ -33,11 +33,11 @@ public function execute() )->addObject( $invoice->getOrder() )->save(); - $this->messageManager->addSuccess(__('The invoice has been captured.')); + $this->messageManager->addSuccessMessage(__('The invoice has been captured.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('Invoice capturing error')); + $this->messageManager->addErrorMessage(__('Invoice capturing error')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php index 359bbafd45105..2d7826807a6c3 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php @@ -112,10 +112,10 @@ public function execute() $resultPage->getConfig()->getTitle()->prepend(__('New Invoice')); return $resultPage; } catch (\Magento\Framework\Exception\LocalizedException $exception) { - $this->messageManager->addError($exception->getMessage()); + $this->messageManager->addErrorMessage($exception->getMessage()); return $this->_redirectToOrder($orderId); } catch (\Exception $exception) { - $this->messageManager->addException($exception, 'Cannot create an invoice.'); + $this->messageManager->addExceptionMessage($exception, 'Cannot create an invoice.'); return $this->_redirectToOrder($orderId); } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php index d804dff5d48a0..71338c818c418 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php @@ -118,7 +118,8 @@ public function execute() $formKeyIsValid = $this->_formKeyValidator->validate($this->getRequest()); $isPost = $this->getRequest()->isPost(); if (!$formKeyIsValid || !$isPost) { - $this->messageManager->addError(__("The invoice can't be saved at this time. Please try again later.")); + $this->messageManager + ->addErrorMessage(__("The invoice can't be saved at this time. Please try again later.")); return $resultRedirect->setPath('sales/order/index'); } @@ -193,9 +194,9 @@ public function execute() $transactionSave->save(); if (!empty($data['do_shipment'])) { - $this->messageManager->addSuccess(__('You created the invoice and shipment.')); + $this->messageManager->addSuccessMessage(__('You created the invoice and shipment.')); } else { - $this->messageManager->addSuccess(__('The invoice has been created.')); + $this->messageManager->addSuccessMessage(__('The invoice has been created.')); } // send invoice/shipment emails @@ -205,7 +206,7 @@ public function execute() } } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError(__('We can\'t send the invoice email right now.')); + $this->messageManager->addErrorMessage(__('We can\'t send the invoice email right now.')); } if ($shipment) { try { @@ -214,15 +215,17 @@ public function execute() } } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError(__('We can\'t send the shipment right now.')); + $this->messageManager->addErrorMessage(__('We can\'t send the shipment right now.')); } } $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getCommentText(true); return $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__("The invoice can't be saved at this time. Please try again later.")); + $this->messageManager->addErrorMessage( + __("The invoice can't be saved at this time. Please try again later.") + ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); } return $resultRedirect->setPath('sales/*/new', ['order_id' => $orderId]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/VoidAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/VoidAction.php index 6c91001cbb3da..4888ed555234c 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/VoidAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/VoidAction.php @@ -34,11 +34,11 @@ public function execute() )->addObject( $invoice->getOrder() )->save(); - $this->messageManager->addSuccess(__('The invoice has been voided.')); + $this->messageManager->addSuccessMessage(__('The invoice has been voided.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('Invoice voiding error')); + $this->messageManager->addErrorMessage(__('Invoice voiding error')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php index 8488e402caf69..32cf6dbfc7c35 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php @@ -61,13 +61,13 @@ protected function massAction(AbstractCollection $collection) $countNonCancelOrder = $collection->count() - $countCancelOrder; if ($countNonCancelOrder && $countCancelOrder) { - $this->messageManager->addError(__('%1 order(s) cannot be canceled.', $countNonCancelOrder)); + $this->messageManager->addErrorMessage(__('%1 order(s) cannot be canceled.', $countNonCancelOrder)); } elseif ($countNonCancelOrder) { - $this->messageManager->addError(__('You cannot cancel the order(s).')); + $this->messageManager->addErrorMessage(__('You cannot cancel the order(s).')); } if ($countCancelOrder) { - $this->messageManager->addSuccess(__('We canceled %1 order(s).', $countCancelOrder)); + $this->messageManager->addSuccessMessage(__('We canceled %1 order(s).', $countCancelOrder)); } $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath($this->getComponentRefererUrl()); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php index e894957dc8b6c..241255f68e39f 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php @@ -62,13 +62,13 @@ protected function massAction(AbstractCollection $collection) $countNonHoldOrder = $collection->count() - $countHoldOrder; if ($countNonHoldOrder && $countHoldOrder) { - $this->messageManager->addError(__('%1 order(s) were not put on hold.', $countNonHoldOrder)); + $this->messageManager->addErrorMessage(__('%1 order(s) were not put on hold.', $countNonHoldOrder)); } elseif ($countNonHoldOrder) { - $this->messageManager->addError(__('No order(s) were put on hold.')); + $this->messageManager->addErrorMessage(__('No order(s) were put on hold.')); } if ($countHoldOrder) { - $this->messageManager->addSuccess(__('You have put %1 order(s) on hold.', $countHoldOrder)); + $this->messageManager->addSuccessMessage(__('You have put %1 order(s) on hold.', $countHoldOrder)); } $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php index 2eb54c9814ef7..5e8ea9c4ad071 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php @@ -64,15 +64,15 @@ protected function massAction(AbstractCollection $collection) $countNonUnHoldOrder = $collection->count() - $countUnHoldOrder; if ($countNonUnHoldOrder && $countUnHoldOrder) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('%1 order(s) were not released from on hold status.', $countNonUnHoldOrder) ); } elseif ($countNonUnHoldOrder) { - $this->messageManager->addError(__('No order(s) were released from on hold status.')); + $this->messageManager->addErrorMessage(__('No order(s) were released from on hold status.')); } if ($countUnHoldOrder) { - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('%1 order(s) have been released from on hold status.', $countUnHoldOrder) ); } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/PdfDocumentsMassAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/PdfDocumentsMassAction.php index c53e4d48925c0..eeda8699a8b10 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/PdfDocumentsMassAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/PdfDocumentsMassAction.php @@ -27,7 +27,7 @@ public function execute() $collection = $this->filter->getCollection($this->getOrderCollection()->create()); return $this->massAction($collection); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); return $resultRedirect->setPath($this->redirectUrl); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfcreditmemos.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfcreditmemos.php index f96e2fd09c2b0..e731c54d8305a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfcreditmemos.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfcreditmemos.php @@ -76,18 +76,22 @@ public function __construct( * * @param AbstractCollection $collection * @return ResponseInterface|ResultInterface + * @throws \Exception */ protected function massAction(AbstractCollection $collection) { $creditmemoCollection = $this->collectionFactory->create()->setOrderFilter(['in' => $collection->getAllIds()]); if (!$creditmemoCollection->getSize()) { - $this->messageManager->addError(__('There are no printable documents related to selected orders.')); + $this->messageManager->addErrorMessage(__('There are no printable documents related to selected orders.')); return $this->resultRedirectFactory->create()->setPath($this->getComponentRefererUrl()); } + $pdf = $this->pdfCreditmemo->getPdf($creditmemoCollection->getItems()); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('creditmemo%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfCreditmemo->getPdf($creditmemoCollection->getItems())->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfdocs.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfdocs.php index d68cfe696b0ef..f641a7bde7335 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfdocs.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfdocs.php @@ -113,6 +113,7 @@ public function __construct( * @return ResponseInterface|\Magento\Backend\Model\View\Result\Redirect * * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @throws \Exception */ protected function massAction(AbstractCollection $collection) { @@ -134,7 +135,7 @@ protected function massAction(AbstractCollection $collection) } if (empty($documents)) { - $this->messageManager->addError(__('There are no printable documents related to selected orders.')); + $this->messageManager->addErrorMessage(__('There are no printable documents related to selected orders.')); return $this->resultRedirectFactory->create()->setPath($this->getComponentRefererUrl()); } @@ -142,10 +143,11 @@ protected function massAction(AbstractCollection $collection) foreach ($documents as $document) { $pdf->pages = array_merge($pdf->pages, $document->pages); } + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; return $this->fileFactory->create( sprintf('docs%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $pdf->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfinvoices.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfinvoices.php index fee124a91410d..9e637c8883485 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfinvoices.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfinvoices.php @@ -75,17 +75,21 @@ public function __construct( * * @param AbstractCollection $collection * @return ResponseInterface|ResultInterface + * @throws \Exception */ protected function massAction(AbstractCollection $collection) { $invoicesCollection = $this->collectionFactory->create()->setOrderFilter(['in' => $collection->getAllIds()]); if (!$invoicesCollection->getSize()) { - $this->messageManager->addError(__('There are no printable documents related to selected orders.')); + $this->messageManager->addErrorMessage(__('There are no printable documents related to selected orders.')); return $this->resultRedirectFactory->create()->setPath($this->getComponentRefererUrl()); } + $pdf = $this->pdfInvoice->getPdf($invoicesCollection->getItems()); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('invoice%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfInvoice->getPdf($invoicesCollection->getItems())->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfshipments.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfshipments.php index 1aa5bfdb83878..6ce2557703ece 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfshipments.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Pdfshipments.php @@ -77,6 +77,7 @@ public function __construct( * * @param AbstractCollection $collection * @return ResponseInterface|\Magento\Backend\Model\View\Result\Redirect + * @throws \Exception */ protected function massAction(AbstractCollection $collection) { @@ -84,12 +85,16 @@ protected function massAction(AbstractCollection $collection) ->create() ->setOrderFilter(['in' => $collection->getAllIds()]); if (!$shipmentsCollection->getSize()) { - $this->messageManager->addError(__('There are no printable documents related to selected orders.')); + $this->messageManager->addErrorMessage(__('There are no printable documents related to selected orders.')); return $this->resultRedirectFactory->create()->setPath($this->getComponentRefererUrl()); } + + $pdf = $this->pdfShipment->getPdf($shipmentsCollection->getItems()); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('packingslip%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfShipment->getPdf($shipmentsCollection->getItems())->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php index 88abdb76d26f7..09a52a113617a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php @@ -53,15 +53,15 @@ public function execute() throw new \Exception(sprintf('Action "%s" is not supported.', $action)); } $this->orderRepository->save($order); - $this->messageManager->addSuccess($message); + $this->messageManager->addSuccessMessage($message); } else { $resultRedirect->setPath('sales/*/'); return $resultRedirect; } } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t update the payment right now.')); + $this->messageManager->addErrorMessage(__('We can\'t update the payment right now.')); $this->logger->critical($e); } $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getEntityId()]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php index 89820b41a68da..3b98d206d5f66 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/AssignPost.php @@ -26,18 +26,18 @@ public function execute() if ($status && $status->getStatus()) { try { $status->assignState($state, $isDefault, $visibleOnFront); - $this->messageManager->addSuccess(__('You assigned the order status.')); + $this->messageManager->addSuccessMessage(__('You assigned the order status.')); return $resultRedirect->setPath('sales/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while assigning the order status.') ); } } else { - $this->messageManager->addError(__('We can\'t find this order status.')); + $this->messageManager->addErrorMessage(__('We can\'t find this order status.')); } return $resultRedirect->setPath('sales/*/assign'); } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Edit.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Edit.php index 69e051b20c4dd..7a4a1918bc1a2 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Edit.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Edit.php @@ -48,7 +48,7 @@ public function execute() $resultPage->getConfig()->getTitle()->prepend(__('Edit Order Status')); return $resultPage; } else { - $this->messageManager->addError(__('We can\'t find this order status.')); + $this->messageManager->addErrorMessage(__('We can\'t find this order status.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('sales/'); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php index 849a7e2d0c817..bd0ad771815b1 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Save.php @@ -40,7 +40,8 @@ public function execute() $status = $this->_objectManager->create(\Magento\Sales\Model\Order\Status::class)->load($statusCode); // check if status exist if ($isNew && $status->getStatus()) { - $this->messageManager->addError(__('We found another order status with the same order status code.')); + $this->messageManager + ->addErrorMessage(__('We found another order status with the same order status code.')); $this->_getSession()->setFormData($data); return $resultRedirect->setPath('sales/*/new'); } @@ -49,23 +50,33 @@ public function execute() try { $status->save(); - $this->messageManager->addSuccess(__('You saved the order status.')); + $this->messageManager->addSuccessMessage(__('You saved the order status.')); return $resultRedirect->setPath('sales/*/'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('We can\'t add the order status right now.') ); } $this->_getSession()->setFormData($data); - if ($isNew) { - return $resultRedirect->setPath('sales/*/new'); - } else { - return $resultRedirect->setPath('sales/*/edit', ['status' => $this->getRequest()->getParam('status')]); - } + return $this->getRedirect($resultRedirect, $isNew); } return $resultRedirect->setPath('sales/*/'); } + + /** + * @param \Magento\Backend\Model\View\Result\Redirect $resultRedirect + * @param bool $isNew + * @return \Magento\Backend\Model\View\Result\Redirect + */ + private function getRedirect(\Magento\Backend\Model\View\Result\Redirect $resultRedirect, $isNew) + { + if ($isNew) { + return $resultRedirect->setPath('sales/*/new'); + } else { + return $resultRedirect->setPath('sales/*/edit', ['status' => $this->getRequest()->getParam('status')]); + } + } } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php index 04db430e1ffa4..2723d483dd38b 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Status/Unassign.php @@ -18,17 +18,17 @@ public function execute() if ($status) { try { $status->unassignState($state); - $this->messageManager->addSuccess(__('You have unassigned the order status.')); + $this->messageManager->addSuccessMessage(__('You have unassigned the order status.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $e, __('Something went wrong while unassigning the order.') ); } } else { - $this->messageManager->addError(__('We can\'t find this order status.')); + $this->messageManager->addErrorMessage(__('We can\'t find this order status.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Unhold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Unhold.php index 752ab088689c8..8d7f27069eeb7 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Unhold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Unhold.php @@ -23,7 +23,7 @@ public function execute() { $resultRedirect = $this->resultRedirectFactory->create(); if (!$this->isValidPostRequest()) { - $this->messageManager->addError(__('Can\'t unhold order.')); + $this->messageManager->addErrorMessage(__('Can\'t unhold order.')); return $resultRedirect->setPath('sales/*/'); } $order = $this->_initOrder(); @@ -32,12 +32,12 @@ public function execute() if (!$order->canUnhold()) { throw new \Magento\Framework\Exception\LocalizedException(__('Can\'t unhold order.')); } - $this->orderManagement->unhold($order->getEntityId()); - $this->messageManager->addSuccess(__('You released the order from holding status.')); + $this->orderManagement->unHold($order->getEntityId()); + $this->messageManager->addSuccessMessage(__('You released the order from holding status.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('The order was not on hold.')); + $this->messageManager->addErrorMessage(__('The order was not on hold.')); } $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getId()]); return $resultRedirect; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/View.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/View.php index 7be17f865312e..8906d11dd776f 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/View.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/View.php @@ -31,7 +31,7 @@ public function execute() $resultPage->getConfig()->getTitle()->prepend(__('Orders')); } catch (\Exception $e) { $this->logger->critical($e); - $this->messageManager->addError(__('Exception occurred during order load')); + $this->messageManager->addErrorMessage(__('Exception occurred during order load')); $resultRedirect->setPath('sales/order/index'); return $resultRedirect; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/View/Giftmessage/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/View/Giftmessage/Save.php index 61dc325c05c91..88fdbe58271f6 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/View/Giftmessage/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/View/Giftmessage/Save.php @@ -18,9 +18,9 @@ public function execute() $this->getRequest()->getParam('giftmessage') )->saveAllInOrder(); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('Something went wrong while saving the gift message.')); + $this->messageManager->addErrorMessage(__('Something went wrong while saving the gift message.')); } if ($this->getRequest()->getParam('type') == 'order_item') { diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/VoidPayment.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/VoidPayment.php index 3e7ce50f9a6f1..4ead8885087c3 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/VoidPayment.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/VoidPayment.php @@ -21,11 +21,11 @@ public function execute() // workaround for backwards compatibility $order->getPayment()->void(new \Magento\Framework\DataObject()); $order->save(); - $this->messageManager->addSuccess(__('The payment has been voided.')); + $this->messageManager->addSuccessMessage(__('The payment has been voided.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t void the payment right now.')); + $this->messageManager->addErrorMessage(__('We can\'t void the payment right now.')); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); } $resultRedirect->setPath('sales/*/view', ['order_id' => $order->getId()]); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/Pdfshipments.php b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/Pdfshipments.php index b10dca908fe6a..4a7c6e0533e33 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/Pdfshipments.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/Pdfshipments.php @@ -70,9 +70,12 @@ public function __construct( */ public function massAction(AbstractCollection $collection) { + $pdf = $this->pdfShipment->getPdf($collection); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->fileFactory->create( sprintf('packingslip%s.pdf', $this->dateTime->date('Y-m-d_H-i-s')), - $this->pdfShipment->getPdf($collection)->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/PrintAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/PrintAction.php index da8646a0c30b2..1e49fe7eff60a 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/PrintAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Shipment/AbstractShipment/PrintAction.php @@ -48,6 +48,7 @@ public function __construct( /** * @return ResponseInterface|\Magento\Backend\Model\View\Result\Forward + * @throws \Exception */ public function execute() { @@ -63,9 +64,11 @@ public function execute() $date = $this->_objectManager->get( \Magento\Framework\Stdlib\DateTime\DateTime::class )->date('Y-m-d_H-i-s'); + $fileContent = ['type' => 'string', 'value' => $pdf->render(), 'rm' => true]; + return $this->_fileFactory->create( 'packingslip' . $date . '.pdf', - $pdf->render(), + $fileContent, DirectoryList::VAR_DIR, 'application/pdf' ); diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Transactions.php b/app/code/Magento/Sales/Controller/Adminhtml/Transactions.php index d5c1186964076..e344b258c5198 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Transactions.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Transactions.php @@ -82,7 +82,7 @@ protected function _initTransaction() ); if (!$txn->getId()) { - $this->messageManager->addError(__('Please correct the transaction ID and try again.')); + $this->messageManager->addErrorMessage(__('Please correct the transaction ID and try again.')); $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); return false; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Fetch.php b/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Fetch.php index e77c65716fdd7..1b72ce3f13e33 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Fetch.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Transactions/Fetch.php @@ -38,11 +38,11 @@ public function execute() ->setOrder($txn->getOrder()) ->importTransactionInfo($txn); $txn->save(); - $this->messageManager->addSuccess(__('The transaction details have been updated.')); + $this->messageManager->addSuccessMessage(__('The transaction details have been updated.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError(__('We can\'t update the transaction details.')); + $this->messageManager->addErrorMessage(__('We can\'t update the transaction details.')); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); } diff --git a/app/code/Magento/Sales/Helper/Guest.php b/app/code/Magento/Sales/Helper/Guest.php index 8407ce5a8d7cb..a3f2ac6ba3556 100644 --- a/app/code/Magento/Sales/Helper/Guest.php +++ b/app/code/Magento/Sales/Helper/Guest.php @@ -165,7 +165,7 @@ public function loadValidOrder(App\RequestInterface $request) $this->coreRegistry->register('current_order', $order); return true; } catch (InputException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return $this->resultRedirectFactory->create()->setPath('sales/guest/form'); } } diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 07716ff8274c6..b690395ebab7c 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -1075,7 +1075,7 @@ public function addProducts(array $products) try { $this->addProduct($productId, $config); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { return $e; } @@ -1956,21 +1956,17 @@ public function createOrder() */ protected function _validate() { - $customerId = $this->getSession()->getCustomerId(); - if ($customerId === null) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please select a customer')); - } - if (!$this->getSession()->getStore()->getId()) { throw new \Magento\Framework\Exception\LocalizedException(__('Please select a store')); } $items = $this->getQuote()->getAllItems(); - if (count($items) == 0) { + if (count($items) === 0) { $this->_errors[] = __('Please specify order items.'); } foreach ($items as $item) { + /** @var \Magento\Quote\Model\Quote\Item $item */ $messages = $item->getMessage(false); if ($item->getHasError() && is_array($messages) && !empty($messages)) { $this->_errors = array_merge($this->_errors, $messages); @@ -2002,7 +1998,7 @@ protected function _validate() $logger = ObjectManager::getInstance()->get(LoggerInterface::class); foreach ($this->_errors as $error) { $logger->error($error); - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); } throw new \Magento\Framework\Exception\LocalizedException(__('Validation is failed.')); diff --git a/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php b/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php index 8ba0b5b071395..4e068eb571deb 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php +++ b/app/code/Magento/Sales/Model/AdminOrder/EmailSender.php @@ -55,7 +55,7 @@ public function send(Order $order) $this->orderSender->send($order); } catch (\Magento\Framework\Exception\MailException $exception) { $this->logger->critical($exception); - $this->messageManager->addWarning( + $this->messageManager->addWarningMessage( __('You did not email your customer. Please check your email settings.') ); return false; diff --git a/app/code/Magento/Sales/Model/Config/Ordered.php b/app/code/Magento/Sales/Model/Config/Ordered.php index 8c5ddb8e07df7..bae6223ee7d5e 100644 --- a/app/code/Magento/Sales/Model/Config/Ordered.php +++ b/app/code/Magento/Sales/Model/Config/Ordered.php @@ -167,13 +167,8 @@ function ($a, $b) { if (!isset($a['sort_order']) || !isset($b['sort_order'])) { return 0; } - if ($a['sort_order'] > $b['sort_order']) { - return 1; - } elseif ($a['sort_order'] < $b['sort_order']) { - return -1; - } else { - return 0; - } + + return $a['sort_order'] <=> $b['sort_order']; } ); } diff --git a/app/code/Magento/Sales/Model/EmailSenderHandler.php b/app/code/Magento/Sales/Model/EmailSenderHandler.php index fe8f1685fe525..7c7005bf0da75 100644 --- a/app/code/Magento/Sales/Model/EmailSenderHandler.php +++ b/app/code/Magento/Sales/Model/EmailSenderHandler.php @@ -90,6 +90,9 @@ public function sendEmails() if ($this->globalConfig->getValue('sales_email/general/async_sending')) { $this->entityCollection->addFieldToFilter('send_email', ['eq' => 1]); $this->entityCollection->addFieldToFilter('email_sent', ['null' => true]); + $this->entityCollection->setPageSize( + $this->globalConfig->getValue('sales_email/general/sending_limit') + ); /** @var \Magento\Store\Api\Data\StoreInterface[] $stores */ $stores = $this->getStores(clone $this->entityCollection); diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index d3b8edbed2eea..7372d9715c725 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -1013,8 +1013,24 @@ public function addStatusToHistory($status, $comment = '', $isCustomerNotified = * @param string $comment * @param bool|string $status * @return OrderStatusHistoryInterface + * @deprecated + * @see addCommentToStatusHistory */ public function addStatusHistoryComment($comment, $status = false) + { + return $this->addCommentToStatusHistory($comment, $status, false); + } + + /** + * Add a comment to order status history + * Different or default status may be specified + * + * @param string $comment + * @param bool|string $status + * @param bool $isVisibleOnFront + * @return OrderStatusHistoryInterface + */ + public function addCommentToStatusHistory($comment, $status = false, $isVisibleOnFront = false) { if (false === $status) { $status = $this->getStatus(); @@ -1029,6 +1045,8 @@ public function addStatusHistoryComment($comment, $status = false) $comment )->setEntityName( $this->entityType + )->setIsVisibleOnFront( + $isVisibleOnFront ); $this->addStatusHistory($history); return $history; diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index 19aac897c21e2..7ea7e4af3c36e 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -469,13 +469,6 @@ public function getStateName($stateId = null) */ public function setShippingAmount($amount) { - // base shipping amount calculated in total model - // $amount = $this->getStore()->round($amount); - // $this->setData('base_shipping_amount', $amount); - // - // $amount = $this->getStore()->round( - // $amount*$this->getOrder()->getStoreToOrderRate() - // ); return $this->setData(CreditmemoInterface::SHIPPING_AMOUNT, $amount); } @@ -582,13 +575,6 @@ public function getCommentsCollection($reload = false) { $collection = $this->_commentCollectionFactory->create()->setCreditmemoFilter($this->getId()) ->setCreatedAtOrder(); -// -// $this->setComments($comments); -// /** -// * When credit memo created with adding comment, -// * comments collection must be loaded before we added this comment. -// */ -// $this->getComments()->load(); if ($this->getId()) { foreach ($collection as $comment) { diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoRepository.php b/app/code/Magento/Sales/Model/Order/CreditmemoRepository.php index 1d82c17549140..ce4a0aa5b3e3a 100644 --- a/app/code/Magento/Sales/Model/Order/CreditmemoRepository.php +++ b/app/code/Magento/Sales/Model/Order/CreditmemoRepository.php @@ -7,12 +7,13 @@ namespace Magento\Sales\Model\Order; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; -use Magento\Sales\Model\ResourceModel\Metadata; -use Magento\Sales\Api\Data\CreditmemoSearchResultInterfaceFactory as SearchResultFactory; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Sales\Api\Data\CreditmemoSearchResultInterfaceFactory as SearchResultFactory; +use Magento\Sales\Model\ResourceModel\Metadata; /** * Repository class for @see \Magento\Sales\Api\Data\CreditmemoInterface @@ -139,6 +140,8 @@ public function save(\Magento\Sales\Api\Data\CreditmemoInterface $entity) try { $this->metadata->getMapper()->save($entity); $this->registry[$entity->getEntityId()] = $entity; + } catch (LocalizedException $e) { + throw new CouldNotSaveException(__($e->getMessage()), $e); } catch (\Exception $e) { throw new CouldNotSaveException(__("The credit memo couldn't be saved."), $e); } diff --git a/app/code/Magento/Sales/Model/Order/Payment/Transaction/Repository.php b/app/code/Magento/Sales/Model/Order/Payment/Transaction/Repository.php index 3caae611d9551..a602fe54363ed 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Transaction/Repository.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Transaction/Repository.php @@ -13,14 +13,11 @@ use Magento\Framework\Data\Collection; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Sales\Api\Data\TransactionInterface; -use Magento\Sales\Api\OrderPaymentRepositoryInterface; -use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\Data\TransactionSearchResultInterfaceFactory as SearchResultFactory; use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\EntityStorage; use Magento\Sales\Model\EntityStorageFactory; -use Magento\Sales\Model\Order\Payment; use Magento\Sales\Model\ResourceModel\Metadata; -use Magento\Sales\Api\Data\TransactionSearchResultInterfaceFactory as SearchResultFactory; use Magento\Sales\Model\ResourceModel\Order\Payment\Transaction as TransactionResource; /** @@ -95,7 +92,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function get($id) { @@ -117,12 +114,10 @@ public function get($id) /** * @param int $transactionType * @param int $paymentId - * @param int $orderId * @return bool|\Magento\Framework\Model\AbstractModel|mixed * @throws \Magento\Framework\Exception\InputException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function getByTransactionType($transactionType, $paymentId, $orderId) + public function getByTransactionType($transactionType, $paymentId) { $identityFieldsForCache = [$transactionType, $paymentId]; $cacheStorage = 'txn_type'; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index bb6078e425900..7f5d880915cfc 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -127,7 +127,8 @@ public function draw() 'feed' => 35, ]; - if ($option['value']) { + // Checking whether option value is not null + if ($option['value']!= null) { if (isset($option['print_value'])) { $printValue = $option['print_value']; } else { diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php index 0e6f345e19bc3..433a4b7e314f4 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php @@ -89,7 +89,7 @@ public function draw() ]; // draw options value - if ($option['value']) { + if ($option['value']!= null) { $printValue = isset( $option['print_value'] ) ? $option['print_value'] : $this->filterManager->stripTags( diff --git a/app/code/Magento/Sales/Model/Order/Shipment.php b/app/code/Magento/Sales/Model/Order/Shipment.php index e23d7eaef2f0a..8be12ec6ad57f 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment.php +++ b/app/code/Magento/Sales/Model/Order/Shipment.php @@ -9,6 +9,7 @@ use Magento\Sales\Api\Data\ShipmentInterface; use Magento\Sales\Model\AbstractModel; use Magento\Sales\Model\EntityInterface; +use Magento\Sales\Model\ResourceModel\Order\Shipment\Comment\Collection as CommentsCollection; /** * Sales order shipment model @@ -94,9 +95,14 @@ class Shipment extends AbstractModel implements EntityInterface, ShipmentInterfa protected $orderRepository; /** - * @var \Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection|null + * @var \Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection */ - private $tracksCollection = null; + private $tracksCollection; + + /** + * @var CommentsCollection + */ + private $commentsCollection; /** * @param \Magento\Framework\Model\Context $context @@ -414,43 +420,45 @@ public function addTrack(\Magento\Sales\Model\Order\Shipment\Track $track) public function addComment($comment, $notify = false, $visibleOnFront = false) { if (!$comment instanceof \Magento\Sales\Model\Order\Shipment\Comment) { - $comment = $this->_commentFactory->create()->setComment( - $comment - )->setIsCustomerNotified( - $notify - )->setIsVisibleOnFront( - $visibleOnFront - ); + $comment = $this->_commentFactory->create() + ->setComment($comment) + ->setIsCustomerNotified($notify) + ->setIsVisibleOnFront($visibleOnFront); } - $comment->setShipment($this)->setParentId($this->getId())->setStoreId($this->getStoreId()); + $comment->setShipment($this) + ->setParentId($this->getId()) + ->setStoreId($this->getStoreId()); if (!$comment->getId()) { $this->getCommentsCollection()->addItem($comment); } + $comments = $this->getComments(); + $comments[] = $comment; + $this->setComments($comments); $this->_hasDataChanges = true; return $this; } /** - * Retrieve comments collection. + * Retrieves comments collection. * * @param bool $reload - * @return \Magento\Sales\Model\ResourceModel\Order\Shipment\Comment\Collection + * @return CommentsCollection */ public function getCommentsCollection($reload = false) { - if (!$this->hasData(ShipmentInterface::COMMENTS) || $reload) { - $comments = $this->_commentCollectionFactory->create() - ->setShipmentFilter($this->getId()) - ->setCreatedAtOrder(); - $this->setComments($comments); - + if ($this->commentsCollection === null || $reload) { + $this->commentsCollection = $this->_commentCollectionFactory->create(); if ($this->getId()) { - foreach ($this->getComments() as $comment) { + $this->commentsCollection->setShipmentFilter($this->getId()) + ->setCreatedAtOrder(); + + foreach ($this->commentsCollection as $comment) { $comment->setShipment($this); } } } - return $this->getComments(); + + return $this->commentsCollection; } /** @@ -514,7 +522,7 @@ public function getPackages() } /** - * {@inheritdoc} + * @inheritdoc * @codeCoverageIgnore */ public function setPackages(array $packages = null) @@ -611,7 +619,7 @@ public function getCreatedAt() } /** - * {@inheritdoc} + * @inheritdoc */ public function setCreatedAt($createdAt) { @@ -743,7 +751,7 @@ public function setComments($comments = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function setStoreId($id) { @@ -751,7 +759,7 @@ public function setStoreId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalWeight($totalWeight) { @@ -759,7 +767,7 @@ public function setTotalWeight($totalWeight) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTotalQty($qty) { @@ -767,7 +775,7 @@ public function setTotalQty($qty) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEmailSent($emailSent) { @@ -775,7 +783,7 @@ public function setEmailSent($emailSent) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderId($id) { @@ -783,7 +791,7 @@ public function setOrderId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCustomerId($id) { @@ -791,7 +799,7 @@ public function setCustomerId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingAddressId($id) { @@ -799,7 +807,7 @@ public function setShippingAddressId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBillingAddressId($id) { @@ -807,7 +815,7 @@ public function setBillingAddressId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShipmentStatus($shipmentStatus) { @@ -815,7 +823,7 @@ public function setShipmentStatus($shipmentStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setIncrementId($id) { @@ -823,7 +831,7 @@ public function setIncrementId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setUpdatedAt($timestamp) { @@ -831,7 +839,7 @@ public function setUpdatedAt($timestamp) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\ShipmentExtensionInterface|null */ @@ -841,7 +849,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Sales\Api\Data\ShipmentExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php index c0a3f84e8846d..4a3e834a69a3d 100644 --- a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php +++ b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory.php @@ -5,6 +5,7 @@ */ namespace Magento\Sales\Model\Order; +use Magento\Framework\App\ObjectManager; use Magento\Sales\Api\Data\ShipmentInterface; use Magento\Sales\Api\Data\ShipmentItemCreationInterface; use Magento\Sales\Api\Data\ShipmentPackageCreationInterface; @@ -15,6 +16,7 @@ use Magento\Sales\Api\Data\ShipmentCommentCreationInterface; use Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface; use Magento\Sales\Api\Data\OrderItemInterface; +use Magento\Sales\Model\Order\ShipmentDocumentFactory\ExtensionAttributesProcessor; /** * Class ShipmentDocumentFactory @@ -39,21 +41,30 @@ class ShipmentDocumentFactory */ private $hydratorPool; + /** + * @var ExtensionAttributesProcessor + */ + private $extensionAttributesProcessor; + /** * ShipmentDocumentFactory constructor. * * @param ShipmentFactory $shipmentFactory * @param HydratorPool $hydratorPool * @param TrackFactory $trackFactory + * @param ExtensionAttributesProcessor $extensionAttributesProcessor */ public function __construct( ShipmentFactory $shipmentFactory, HydratorPool $hydratorPool, - TrackFactory $trackFactory + TrackFactory $trackFactory, + ExtensionAttributesProcessor $extensionAttributesProcessor = null ) { $this->shipmentFactory = $shipmentFactory; $this->trackFactory = $trackFactory; $this->hydratorPool = $hydratorPool; + $this->extensionAttributesProcessor = $extensionAttributesProcessor ?: ObjectManager::getInstance() + ->get(ExtensionAttributesProcessor::class); } /** @@ -88,6 +99,10 @@ public function create( $shipmentItems ); + if (null !== $arguments) { + $this->extensionAttributesProcessor->execute($shipment, $arguments); + } + foreach ($tracks as $track) { $hydrator = $this->hydratorPool->getHydrator( \Magento\Sales\Api\Data\ShipmentTrackCreationInterface::class diff --git a/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory/ExtensionAttributesProcessor.php b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory/ExtensionAttributesProcessor.php new file mode 100644 index 0000000000000..c4c38a234dab1 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/ShipmentDocumentFactory/ExtensionAttributesProcessor.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order\ShipmentDocumentFactory; + +use Magento\Framework\Reflection\ExtensionAttributesProcessor as AttributesProcessor; +use Magento\Sales\Api\Data\ShipmentCreationArgumentsExtensionInterface; +use Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface; +use Magento\Sales\Api\Data\ShipmentExtensionFactory; +use Magento\Sales\Api\Data\ShipmentExtensionInterface; +use Magento\Sales\Api\Data\ShipmentInterface; + +/** + * Build and set extension attributes for shipment. + */ +class ExtensionAttributesProcessor +{ + /** + * @var ShipmentExtensionFactory + */ + private $shipmentExtensionFactory; + + /** + * @var ExtensionAttributesProcessor + */ + private $extensionAttributesProcessor; + + /** + * @param ShipmentExtensionFactory $shipmentExtensionFactory + * @param AttributesProcessor $extensionAttributesProcessor + */ + public function __construct( + ShipmentExtensionFactory $shipmentExtensionFactory, + AttributesProcessor $extensionAttributesProcessor + ) { + $this->shipmentExtensionFactory = $shipmentExtensionFactory; + $this->extensionAttributesProcessor = $extensionAttributesProcessor; + } + + /** + * @param ShipmentInterface $shipment + * @param ShipmentCreationArgumentsInterface $arguments + * @return void + */ + public function execute( + ShipmentInterface $shipment, + ShipmentCreationArgumentsInterface $arguments + ): void { + $shipmentExtensionAttributes = []; + if (null !== $shipment->getExtensionAttributes()) { + $shipmentExtensionAttributes = $this->extensionAttributesProcessor->buildOutputDataArray( + $shipment->getExtensionAttributes(), + ShipmentExtensionInterface::class + ); + } + $argumentsExtensionAttributes = []; + if (null !== $arguments->getExtensionAttributes()) { + $argumentsExtensionAttributes = $this->extensionAttributesProcessor->buildOutputDataArray( + $arguments->getExtensionAttributes(), + ShipmentCreationArgumentsExtensionInterface::class + ); + } + + $mergedExtensionAttributes = $this->shipmentExtensionFactory->create([ + 'data' => array_merge($shipmentExtensionAttributes, $argumentsExtensionAttributes) + ]); + + $shipment->setExtensionAttributes($mergedExtensionAttributes); + } +} diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php new file mode 100644 index 0000000000000..4cbcfe49f4cc9 --- /dev/null +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdate.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sales\Model\ResourceModel\Order\Payment; + +/** + * Resource for updating encrypted credit card data to the latest cipher + */ +class EncryptionUpdate +{ + const LEGACY_PATTERN = '^[[:digit:]]+:[^%s]:.*$'; + + /** + * @var \Magento\Sales\Model\ResourceModel\Order\Payment + */ + private $paymentResource; + + /** + * @var \Magento\Framework\Encryption\Encryptor + */ + private $encryptor; + + /** + * @param \Magento\Sales\Model\ResourceModel\Order\Payment $paymentResource + * @param \Magento\Framework\Encryption\Encryptor $encryptor + */ + public function __construct( + \Magento\Sales\Model\ResourceModel\Order\Payment $paymentResource, + \Magento\Framework\Encryption\Encryptor $encryptor + ) { + $this->paymentResource = $paymentResource; + $this->encryptor = $encryptor; + } + + /** + * Fetch encrypted credit card numbers using legacy ciphers and re-encrypt with latest cipher + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function reEncryptCreditCardNumbers() + { + $connection = $this->paymentResource->getConnection(); + $table = $this->paymentResource->getMainTable(); + $select = $connection->select()->from($table, ['entity_id', 'cc_number_enc']) + ->where( + 'cc_number_enc REGEXP ?', + sprintf(self::LEGACY_PATTERN, \Magento\Framework\Encryption\Encryptor::CIPHER_LATEST) + )->limit(1000); + + while ($attributeValues = $connection->fetchPairs($select)) { + // save new values + foreach ($attributeValues as $valueId => $value) { + $connection->update( + $table, + ['cc_number_enc' => $this->encryptor->encrypt($this->encryptor->decrypt($value))], + ['entity_id = ?' => (int)$valueId, 'cc_number_enc = ?' => (string)$value] + ); + } + } + } +} diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php index 24f56c0dbd595..e8f2e6e5305f7 100644 --- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php +++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php @@ -100,22 +100,11 @@ public function __construct( * @param int $id Credit Memo Id * @return bool * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function cancel($id) { throw new \Magento\Framework\Exception\LocalizedException(__('You can not cancel Credit Memo')); - try { - $creditmemo = $this->creditmemoRepository->get($id); - $creditmemo->setState(\Magento\Sales\Model\Order\Creditmemo::STATE_CANCELED); - foreach ($creditmemo->getAllItems() as $item) { - $item->cancel(); - } - $this->eventManager->dispatch('sales_order_creditmemo_cancel', ['creditmemo' => $creditmemo]); - $this->creditmemoRepository->save($creditmemo); - } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException(__('Could not cancel creditmemo'), $e); - } - return true; } /** diff --git a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php index d3e13334bb3f9..2716e860243bf 100644 --- a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php +++ b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php @@ -15,11 +15,12 @@ use Magento\Sales\Setup\SalesSetupFactory; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, PatchVersionInterface { /** - * @var \Magento\Framework\Setup\ModuleDataSetupInterface + * @var ModuleDataSetupInterface */ private $moduleDataSetup; @@ -55,10 +56,10 @@ class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, Patch /** * PatchInitial constructor. - * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + * @param ModuleDataSetupInterface $moduleDataSetup */ public function __construct( - \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup, + ModuleDataSetupInterface $moduleDataSetup, SalesSetupFactory $salesSetupFactory, State $state, Config $eavConfig, @@ -82,39 +83,41 @@ public function apply() { $this->state->emulateAreaCode( \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE, - [$this, 'fillQuoteAddressIdInSalesOrderAddress'] + [$this, 'fillQuoteAddressIdInSalesOrderAddress'], + [$this->moduleDataSetup] ); $this->eavConfig->clear(); } /** * Fill quote_address_id in table sales_order_address if it is empty. + * + * @param ModuleDataSetupInterface $setup */ - public function fillQuoteAddressIdInSalesOrderAddress() + public function fillQuoteAddressIdInSalesOrderAddress(ModuleDataSetupInterface $setup) { - $addressCollection = $this->addressCollectionFactory->create(); - $addressCollection->addFieldToFilter('quote_address_id', ['null' => true]); - - /** @var \Magento\Sales\Model\Order\Address $orderAddress */ - foreach ($addressCollection as $orderAddress) { - $orderId = $orderAddress->getParentId(); - $addressType = $orderAddress->getAddressType(); - - /** @var \Magento\Sales\Model\Order $order */ - $order = $this->orderFactory->create()->load($orderId); - $quoteId = $order->getQuoteId(); - $quote = $this->quoteFactory->create()->load($quoteId); - - if ($addressType == \Magento\Sales\Model\Order\Address::TYPE_SHIPPING) { - $quoteAddressId = $quote->getShippingAddress()->getId(); - $orderAddress->setData('quote_address_id', $quoteAddressId); - } elseif ($addressType == \Magento\Sales\Model\Order\Address::TYPE_BILLING) { - $quoteAddressId = $quote->getBillingAddress()->getId(); - $orderAddress->setData('quote_address_id', $quoteAddressId); - } - - $orderAddress->save(); - } + $addressTable = $setup->getTable('sales_order_address'); + $updateOrderAddress = $setup->getConnection() + ->select() + ->joinInner( + ['sales_order' => $setup->getTable('sales_order')], + $addressTable . '.parent_id = sales_order.entity_id', + ['quote_address_id' => 'quote_address.address_id'] + ) + ->joinInner( + ['quote_address' => $setup->getTable('quote_address')], + 'sales_order.quote_id = quote_address.quote_id + AND ' . $addressTable . '.address_type = quote_address.address_type', + [] + ) + ->where( + $addressTable . '.quote_address_id IS NULL' + ); + $updateOrderAddress = $setup->getConnection()->updateFromSelect( + $updateOrderAddress, + $addressTable + ); + $setup->getConnection()->query($updateOrderAddress); } /** diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCreditMemoActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCreditMemoActionGroup.xml index 2a9ef0c948392..58c7752626c8a 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCreditMemoActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminCreditMemoActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Check customer information is correct in credit memo--> <actionGroup name="verifyBasicCreditMemoInformation"> <arguments> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml index f17d3462d06f3..15aff7c751a11 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Check customer information is correct in invoice--> <actionGroup name="verifyBasicInvoiceInformation"> <arguments> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index 25dfdce165aaa..7182f80d96ec8 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Navigate to create order page (New Order -> Create New Customer)--> <actionGroup name="navigateToNewOrderPageNewCustomer"> <arguments> @@ -22,6 +22,37 @@ <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> </actionGroup> + <!--Navigate to create order page (New Order -> Create New Customer)--> + <actionGroup name="navigateToNewOrderPageNewCustomerSingleStore"> + <arguments> + <argument name="storeView" defaultValue="_defaultStore"/> + </arguments> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + </actionGroup> + + <!--Navigate to create order page (New Order -> Select Customer)--> + <actionGroup name="navigateToNewOrderPageExistingCustomer"> + <arguments> + <argument name="customer"/> + </arguments> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <waitForPageLoad stepKey="waitForCustomerGridLoad"/> + <fillField userInput="{{customer.email}}" selector="{{AdminOrderCustomersGridSection.emailInput}}" stepKey="filterEmail"/> + <click selector="{{AdminOrderCustomersGridSection.apply}}" stepKey="applyFilter"/> + <waitForPageLoad stepKey="waitForFilteredCustomerGridLoad"/> + <click selector="{{AdminOrderCustomersGridSection.firstRow}}" stepKey="clickOnCustomer"/> + <waitForPageLoad stepKey="waitForCreateOrderPageLoad" /> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + </actionGroup> + <!--Check the required fields are actually required--> <actionGroup name="checkRequiredFieldsNewOrderForm"> <seeElement selector="{{AdminOrderFormAccountSection.requiredGroup}}" stepKey="seeCustomerGroupRequired"/> @@ -84,6 +115,67 @@ <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> </actionGroup> + <!--Add bundle product to order --> + <actionGroup name="addBundleProductToOrder"> + <arguments> + <argument name="product"/> + <argument name="quantity" type="string" defaultValue="1"/> + </arguments> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickAddProducts"/> + <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillSkuFilterBundle"/> + <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearchBundle"/> + <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> + <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectBundleProduct"/> + <waitForElementVisible selector="{{AdminOrderFormBundleProductSection.quantity}}" stepKey="waitForBundleOptionLoad"/> + <wait time="2" stepKey="waitForOptionsToLoad"/> + <fillField selector="{{AdminOrderFormBundleProductSection.quantity}}" userInput="{{quantity}}" stepKey="fillQuantity"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOk"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> + </actionGroup> + + <!--Add downloadable product to order --> + <actionGroup name="addDownloadableProductToOrder"> + <arguments> + <argument name="product"/> + <argument name="link"/> + <argument name="quantity" defaultValue="1" type="string"/> + </arguments> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickAddProducts"/> + <waitForPageLoad time="30" stepKey="waitForPageLoad1"/> + <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillSkuFilterDownloadable"/> + <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearchDownloadable"/> + <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> + <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectDownloadableProduct"/> + <waitForElementVisible selector="{{AdminOrderFormDownloadableProductSection.optionSelect(link.title)}}" stepKey="waitForLinkLoad"/> + <click selector="{{AdminOrderFormDownloadableProductSection.optionSelect(link.title)}}" stepKey="selectLink"/> + <fillField selector="{{AdminOrderFormDownloadableProductSection.quantity}}" userInput="{{quantity}}" stepKey="setQuantity"/> + <click selector="{{AdminOrderFormDownloadableProductSection.ok}}" stepKey="confirmConfiguration"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> + </actionGroup> + + <!--Add grouped product option to order --> + <actionGroup name="addGroupedProductOptionToOrder"> + <arguments> + <argument name="product"/> + <argument name="option"/> + <argument name="quantity" type="string" defaultValue="1"/> + </arguments> + + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickAddProducts"/> + <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillSkuFilterGrouped"/> + <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearchGrouped"/> + <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> + <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectGroupedProduct"/> + <waitForElementVisible selector="{{AdminOrderFormGroupedProductSection.optionQty(option.sku)}}" stepKey="waitForGroupedOptionLoad"/> + <wait time="2" stepKey="waitForOptionsToLoad"/> + <fillField selector="{{AdminOrderFormGroupedProductSection.optionQty(option.sku)}}" userInput="{{quantity}}" stepKey="fillOptionQuantity"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOk"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> + </actionGroup> + <!--Fill customer billing address--> <actionGroup name="fillOrderCustomerInformation"> <arguments> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml index 7dcb89fdc1628..eed9f80c251c8 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Filter order grid by order id field--> <actionGroup name="filterOrderGridById"> <arguments> @@ -68,4 +68,10 @@ <selectOption selector="{{AdminDataGridHeaderSection.filterFieldSelect('status')}}" userInput="{{status}}" stepKey="fillOrderStatusFilter"/> <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFilters"/> </actionGroup> + + <actionGroup name="AdminOrdersGridClearFiltersActionGroup"> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToGridOrdersPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <conditionalClick selector="{{AdminOrdersGridSection.clearFilters}}" dependentSelector="{{AdminOrdersGridSection.enabledFilters}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Sales/Test/Mftf/Data/AddressData.xml index 800bbfca2f869..920618a70dfb8 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/AddressData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ShippingAddressTX" type="shipping_address"> <data key="firstname">Joe</data> <data key="lastname">Buyer</data> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Sales/Test/Mftf/Data/ConstData.xml index f6855ed09c263..10de6681d1b57 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/ConstData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Const" type="constant"> <data key="one">1</data> <data key="two">2</data> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/OrderData.xml b/app/code/Magento/Sales/Test/Mftf/Data/OrderData.xml index 566fb7d44528e..eb5600f112ea1 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/OrderData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/OrderData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!--Data for order created through UI with simple and configurable order--> <entity name="AdminOrderSimpleConfigurableProduct" type="order"> <data key="subtotal">246.00</data> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/SalesConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/SalesConfigData.xml new file mode 100644 index 0000000000000..3239e17403255 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Data/SalesConfigData.xml @@ -0,0 +1,27 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnabledMinimumOrderAmount500" type="sales_minimum_order"> + <requiredEntity type="active">EnableMinimumOrderCheck</requiredEntity> + <requiredEntity type="amount">MinimumOrderAmount500</requiredEntity> + </entity> + <entity name="EnableMinimumOrderCheck" type="active"> + <data key="value">1</data> + </entity> + <entity name="MinimumOrderAmount500" type="amount"> + <data key="value">500</data> + </entity> + + <entity name="DisabledMinimumOrderAmount" type="sales_minimum_order"> + <requiredEntity type="active">DisableMinimumOrderCheck</requiredEntity> + </entity> + <entity name="DisableMinimumOrderCheck" type="active"> + <data key="value">0</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_config-meta.xml b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_config-meta.xml new file mode 100644 index 0000000000000..2bd084b5457b8 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_config-meta.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="SetSalesMinimumOrder" dataType="sales_minimum_order" type="create" auth="adminFormKey" url="/admin/system_config/save/section/sales/" method="POST"> + <object key="groups" dataType="sales_minimum_order"> + <object key="minimum_order" dataType="sales_minimum_order"> + <object key="fields" dataType="sales_minimum_order"> + <object key="active" dataType="active"> + <field key="value">string</field> + </object> + <object key="amount" dataType="amount"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminCreditMemoNewPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminCreditMemoNewPage.xml index 5a50c807628c6..2d020caa0d107 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminCreditMemoNewPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminCreditMemoNewPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCreditMemoNewPage" url="sales/order_creditmemo/new/order_id/" area="admin" module="Magento_Sales"> <section name="AdminCreditMemoOrderInformationSection"/> <section name="AdminCreditMemoAddressInformationSection"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceDetailsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceDetailsPage.xml index a60e44247fe9c..bf48bc6763348 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceDetailsPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceDetailsPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminInvoiceDetailsPage" url="sales/invoice/view/invoice_id/" area="admin" module="Magento_Sales"> <section name="AdminInvoiceDetailsInformationSection"/> </page> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceNewPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceNewPage.xml index e782fe5194720..d547c9f1eb05f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceNewPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoiceNewPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminInvoiceNewPage" url="sales/order_invoice/new/order_id/" area="admin" module="Magento_Sales"> <section name="AdminInvoiceMainActionsSection"/> <section name="AdminInvoiceOrderInformationSection"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoicesPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoicesPage.xml index 7f7289b2e64fd..3dda74adb9c0f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoicesPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminInvoicesPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminInvoicesPage" url="sales/invoice/" area="admin" module="Magento_Sales"> <section name="AdminInvoicesGridSection"/> <section name="AdminInvoicesFiltersSection"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml index 49520261d857f..333be23dbf346 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminOrderCreatePage" url="sales/order_create/index" area="admin" module="Magento_Sales"> <section name="AdminOrderFormActionSection"/> <section name="AdminOrderFormItemsSection"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderDetailsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderDetailsPage.xml index 690eade8b0c8f..c62144b84aa63 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderDetailsPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderDetailsPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminOrderDetailsPage" url="sales/order/view/order_id/" area="admin" module="Magento_Sales"> <section name="AdminOrderDetailsMainActionsSection"/> <section name="AdminOrderDetailsOrderViewSection"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml index d4d5e78631885..7a9414e3f9aab 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminOrdersPage" url="sales/order/" area="admin" module="Magento_Sales"> <section name="AdminOrdersGridSection"/> </page> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoAddressInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoAddressInformationSection.xml index 85e5faca3a48c..178cd37e6b8d5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoAddressInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoAddressInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreditMemoAddressInformationSection"> <element name="billingAddress" type="text" selector=".order-billing-address address"/> <element name="billingAddressEdit" type="button" selector=".order-billing-address .actions a"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml index 5e3e8fc6e22ec..13f351c064437 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoItemsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreditMemoItemsSection"> <element name="header" type="text" selector="#creditmemo_item_container span.title"/> <element name="itemName" type="text" selector=".order-creditmemo-tables tbody:nth-of-type({{row}}) .col-product .product-title" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoOrderInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoOrderInformationSection.xml index b0be8c6cd6575..5b7f829626587 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoOrderInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoOrderInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreditMemoOrderInformationSection"> <element name="orderId" type="text" selector="div.order-information span.title > a" timeout="60"/> <element name="orderDate" type="text" selector=".order-information table.order-information-table tr:first-of-type > td"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoPaymentShippingSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoPaymentShippingSection.xml index b307a5d1cf4bc..1bfe28b9f045d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoPaymentShippingSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoPaymentShippingSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreditMemoPaymentShippingSection"> <element name="PaymentMethod" type="text" selector=".order-payment-method .order-payment-method-title"/> <element name="CurrencyInformation" type="text" selector=".order-payment-method .order-payment-currency"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml index a49d06545df05..00eb93452edd5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml @@ -7,10 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreditMemoTotalSection"> <element name="subtotalRow" type="text" selector=".order-subtotal-table tbody > tr:nth-of-type({{row}}) td span.price" parameterized="true"/> - <element name="total" type="text" selector="//table[contains(@class,'order-subtotal-table')]/tbody/tr/td[contains(text(), '{{total}}')]/following-sibling::td/span/span[contains(@class, 'price')]" parameterized="true"/> + <element name="total" type="text" selector="//table[contains(@class,'order-subtotal-table')]/tbody/tr/td[contains(text(), '{{total}}')]/following-sibling::td//span[contains(@class, 'price')]" parameterized="true"/> <element name="refundShipping" type="input" selector=".order-subtotal-table tbody input[name='creditmemo[shipping_amount]']"/> <element name="adjustmentRefund" type="input" selector=".order-subtotal-table tbody input[name='creditmemo[adjustment_positive]'"/> <element name="adjustmentFee" type="input" selector=".order-subtotal-table tbody input[name='creditmemo[adjustment_negative]']"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceAddressInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceAddressInformationSection.xml index 63712f24a5de8..14a0d4b8488ea 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceAddressInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceAddressInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceAddressInformationSection"> <element name="billingAddress" type="text" selector=".order-billing-address address"/> <element name="billingAddressEdit" type="button" selector=".order-billing-address .actions a"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceDetailsInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceDetailsInformationSection.xml index 41b36310ebeb1..39071a9eb5899 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceDetailsInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceDetailsInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceDetailsInformationSection"> <element name="orderStatus" type="text" selector="#order_status"/> </section> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml index 56c165bada500..bc0d1cffd5d3e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceItemsSection"> <element name="itemName" type="text" selector=".order-invoice-tables tbody:nth-of-type({{row}}) .product-title" parameterized="true"/> <element name="itemSku" type="text" selector=".order-invoice-tables tbody:nth-of-type({{row}}) .product-sku-block" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml index c1a9718b29b1c..2a241708517bf 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceMainActionsSection"> <element name="submitInvoice" type="button" selector=".action-default.scalable.save.submit-button.primary"/> </section> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceOrderInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceOrderInformationSection.xml index 198a087bffc45..38ca7e683fe56 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceOrderInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceOrderInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceOrderInformationSection"> <element name="orderId" type="text" selector="div.order-information span.title > a" timeout="30"/> <element name="orderDate" type="text" selector=".order-information table.order-information-table tr:first-of-type > td"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicePaymentShippingSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicePaymentShippingSection.xml index 3bb381047bb97..918a8e814b555 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicePaymentShippingSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicePaymentShippingSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoicePaymentShippingSection"> <element name="PaymentMethod" type="text" selector=".order-payment-method .order-payment-method-title"/> <element name="CurrencyInformation" type="text" selector=".order-payment-method .order-payment-currency"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml index db5b12f622b64..f66412c876709 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceTotalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoiceTotalSection"> <element name="subtotalRow" type="text" selector=".order-subtotal-table tbody > tr:nth-of-type({{row}}) td span.price" parameterized="true"/> <element name="total" type="text" selector="//table[contains(@class,'order-subtotal-table')]/tbody/tr/td[contains(text(), '{{total}}')]/following-sibling::td/span/span[contains(@class, 'price')]" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml index 5b55dd3cc1aae..8fb45295bd26e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesFiltersSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoicesFiltersSection"> <element name="orderNum" type="input" selector="input[name='order_increment_id']"/> </section> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesGridSection.xml index 8d2d8750e5045..b8cc79a84db1a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoicesGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminInvoicesGridSection"> <element name="spinner" type="button" selector=".spinner"/> <element name="filter" type="button" selector="#container > div > div.admin__data-grid-header > div:nth-child(1) > div.data-grid-filters-actions-wrap > div > button"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderAddressInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderAddressInformationSection.xml index 2900cb60391be..2632d5f2815e7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderAddressInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderAddressInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderAddressInformationSection"> <element name="billingAddress" type="text" selector=".order-billing-address address"/> <element name="shippingAddress" type="text" selector=".order-shipping-address address"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCommentsTabSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCommentsTabSection.xml index 9a784049e081a..19f447117959a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCommentsTabSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCommentsTabSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderCommentsTabSection"> <element name="orderNotesList" type="text" selector="#Order_History .edit-order-comments .note-list"/> <element name="orderComments" type="text" selector="#Order_History .edit-order-comments-block"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCreditMemosTabSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCreditMemosTabSection.xml index f4835cf02b704..e285f8700a1a7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCreditMemosTabSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCreditMemosTabSection.xml @@ -7,10 +7,11 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderCreditMemosTabSection"> <element name="spinner" type="text" selector="[data-role='spinner'][data-component*='sales_order_view_creditmemo']"/> <element name="gridRow" type="text" selector="#sales_order_view_tabs_order_creditmemos_content .data-grid tbody > tr:nth-of-type({{row}})" parameterized="true"/> <element name="viewGridRow" type="button" selector="#sales_order_view_tabs_order_creditmemos_content .data-grid tbody > tr:nth-of-type({{row}}) a[href*='order_creditmemo/view']" parameterized="true"/> + <element name="gridRowCell" type="text" selector="//div[@id='sales_order_view_tabs_order_creditmemos_content']//tr[{{row}}]//td[count(//div[@id='sales_order_view_tabs_order_creditmemos_content']//tr//th[contains(., '{{column}}')][1]/preceding-sibling::th) +1 ]" parameterized="true"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml new file mode 100644 index 0000000000000..c02a36432851d --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderCustomersGridSection"> + <element name="spinner" type="button" selector=".spinner"/> + <element name="apply" type="button" selector=".action-secondary[title='Search']"/> + <element name="resetFilter" type="button" selector=".action-tertiary[title='Reset Filter']"/> + <element name="emailInput" type="input" selector="#sales_order_create_customer_grid_filter_email"/> + <element name="firstRow" type="button" selector="tr:nth-of-type(1)[data-role='row']"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml index 39feba4694019..a531f423d134c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderDetailsInformationSection"> <element name="orderDate" type="text" selector=".order-information table.order-information-table tr:first-of-type > td"/> <element name="orderStatus" type="text" selector=".order-information table.order-information-table #order_status"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInvoicesSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInvoicesSection.xml index b6b32184841c4..bcf8bdcae7c59 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInvoicesSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInvoicesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderDetailsInvoicesSection"> <element name="spinner" type="button" selector=".spinner"/> <element name="content" type="text" selector="#sales_order_view_tabs_order_invoices_content"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml index eac238584b030..578022217f358 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderDetailsMainActionsSection"> <element name="back" type="button" selector="#back" timeout="30"/> <element name="cancel" type="button" selector="#order-view-cancel-button" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMessagesSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMessagesSection.xml index 31f78db11f90b..1bc3718467102 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMessagesSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderDetailsMessagesSection"> <element name="successMessage" type="text" selector="div.message-success"/> </section> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsOrderViewSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsOrderViewSection.xml index 6623c68099fe7..7f98256fa732a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsOrderViewSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsOrderViewSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderDetailsOrderViewSection"> <element name="information" type="button" selector="#sales_order_view_tabs_order_info"/> <element name="invoices" type="button" selector="#sales_order_view_tabs_order_invoices"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml index 3c920bc6ba0e7..4ab1e3327960c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormAccountSection"> <element name="group" type="select" selector="#group_id"/> <element name="email" type="input" selector="#email"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml index 09b2012841b8b..2f6149dfa1cb7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormActionSection"> <element name="SubmitOrder" type="button" selector="#submit_order_top_button" timeout="30"/> <element name="Cancel" type="button" selector="#reset_order_top_button" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml index b0f7eb21d4dd7..2d1a4d5a4cbae 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormBillingAddressSection"> <element name="NamePrefix" type="input" selector="#order-billing_address_prefix" timeout="30"/> <element name="FirstName" type="input" selector="#order-billing_address_firstname" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBundleProductSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBundleProductSection.xml new file mode 100644 index 0000000000000..562d17f2e8739 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBundleProductSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormBundleProductSection"> + <element name="quantity" type="input" selector="#product_composite_configure_input_qty"/> + <element name="ok" type="button" selector=".modal-header .page-actions button[data-role='action']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml index 87948c38e4328..83d417f6f8555 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormConfigureProductSection"> <element name="optionSelect" type="select" selector="//div[@class='product-options']/div/div/select[../../label[text() = '{{option}}']]" parameterized="true"/> <element name="quantity" type="input" selector="#product_composite_configure_input_qty"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormDownloadableProductSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormDownloadableProductSection.xml new file mode 100644 index 0000000000000..94cb0c57d4ee2 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormDownloadableProductSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormDownloadableProductSection"> + <element name="optionSelect" type="select" selector="//div[contains(@class,'link')]/div/div/input[./../label[contains(text(),{{linkTitle}})]]" parameterized="true"/> + <element name="quantity" type="input" selector="#product_composite_configure_input_qty"/> + <element name="ok" type="button" selector=".modal-header .page-actions button[data-role='action']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormGroupedProductSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormGroupedProductSection.xml new file mode 100644 index 0000000000000..5a25a1bd880f4 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormGroupedProductSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormGroupedProductSection"> + <element name="optionQty" type="input" selector="//td[@class='col-sku'][text()='{{productSku}}']/..//input[contains(@class, 'qty')]" parameterized="true"/> + <element name="ok" type="button" selector=".modal-header .page-actions button[data-role='action']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml index 86288ec7d7ec9..65f9a41c269c9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormItemsSection"> <element name="skuNumber" type="input" selector="#sku_{{row}}" parameterized="true"/> <element name="qty" type="input" selector="#sku_qty_{{row}}" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index bb7c89dd39b6c..e4d329bc85057 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormPaymentSection"> <element name="header" type="text" selector="#order-methods span.title"/> <element name="getShippingMethods" type="text" selector="#order-shipping_method a.action-default" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml index 82ecb023198c7..b79d933268769 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormShippingAddressSection"> <element name="SameAsBilling" type="checkbox" selector="#order-shipping_same_as_billing"/> <element name="NamePrefix" type="input" selector="#order-shipping_address_prefix"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormTotalSection.xml index e44a97b678f89..6f62ce199ecbb 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormTotalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormTotalSection"> <element name="subtotalRow" type="text" selector="#order-totals>table tr.row-totals:nth-of-type({{row}}) span.price" parameterized="true"/> <element name="total" type="text" selector="//tr[contains(@class,'row-totals')]/td[contains(text(), '{{total}}')]/following-sibling::td/span[contains(@class, 'price')]" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml index e00ab7e66b99a..b33276bed527e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderInvoicesTabSection"> <element name="spinner" type="text" selector="[data-role='spinner'][data-component*='sales_order_view_invoice']"/> <element name="gridRow" type="text" selector="#sales_order_view_tabs_order_invoices_content .data-grid tbody > tr:nth-of-type({{row}})" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml index 9807d7364c7c6..53aeeb62c6b70 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderItemsOrderedSection"> <element name="itemProductName" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-product .product-title" parameterized="true"/> <element name="itemProductSku" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-product .product-sku-block" parameterized="true"/> @@ -23,6 +23,7 @@ <element name="productNameColumn" type="text" selector=".edit-order-table .col-product .product-title"/> <element name="productNameOptions" type="text" selector=".edit-order-table .col-product .item-options"/> + <element name="productNameOptionsLink" type="text" selector="//table[contains(@class, 'edit-order-table')]//td[contains(@class, 'col-product')]//a[text() = '{{var1}}']" parameterized="true"/> <element name="productSkuColumn" type="text" selector=".edit-order-table .col-product .product-sku-block"/> <element name="statusColumn" type="text" selector=".edit-order-table .col-status"/> <element name="originalPriceColumn" type="text" selector=".edit-order-table .col-original-price .price"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderPaymentInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderPaymentInformationSection.xml index 15b468d1dfa9b..9299222fd3236 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderPaymentInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderPaymentInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderPaymentInformationSection"> <element name="paymentMethod" type="text" selector=".order-payment-method .order-payment-method-title"/> <element name="paymentCurrency" type="text" selector=".order-payment-method .order-payment-currency"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShipmentsTabSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShipmentsTabSection.xml index 65dbf01aad2e0..70d413d733b8e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShipmentsTabSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShipmentsTabSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderShipmentsTabSection"> <element name="spinner" type="text" selector="[data-role='spinner'][data-component*='sales_order_view_shipment']"/> <element name="gridRow" type="text" selector="#sales_order_view_tabs_order_shipments_content .data-grid tbody > tr:nth-of-type({{row}})" parameterized="true"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShippingInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShippingInformationSection.xml index f29e8a2a7f970..83e5512ef0d27 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShippingInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderShippingInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderShippingInformationSection"> <element name="shippingMethod" type="text" selector=".order-shipping-method .admin__page-section-item-content"/> <element name="shippingPrice" type="text" selector=".order-shipping-method .admin__page-section-item-content .price"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStoreScopeTreeSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStoreScopeTreeSection.xml index 8f31dd1b8e96c..050e1ba8b2df4 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStoreScopeTreeSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStoreScopeTreeSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderStoreScopeTreeSection"> <element name="storeTree" type="text" selector="div.tree-store-scope"/> <element name="storeOption" type="radio" selector="//div[contains(@class, 'tree-store-scope')]//label[contains(text(), '{{name}}')]/preceding-sibling::input" parameterized="true" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml index 5d60886a96a51..9b7356127df69 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderTotalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderTotalSection"> <element name="subTotal" type="text" selector=".order-subtotal-table tbody tr.col-0>td span.price"/> <element name="grandTotal" type="text" selector=".order-subtotal-table tfoot tr.col-0>td span.price"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml index aa744787b0fef..7ece18fb863b7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrdersGridSection"> <element name="spinner" type="button" selector=".spinner"/> <element name="gridLoadingMask" type="button" selector=".admin__data-grid-loading-mask"/> @@ -17,6 +17,7 @@ <element name="filters" type="button" selector="button[data-action='grid-filter-expand']" timeout="30"/> <element name="idFilter" type="input" selector=".admin__data-grid-filters input[name='increment_id']"/> <element name="billToNameFilter" type="input" selector=".admin__data-grid-filters input[name='billing_name']"/> + <element name="enabledFilters" type="block" selector=".admin__data-grid-header .admin__data-grid-filters-current._show"/> <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> <element name="rowViewAction" type="button" selector=".data-grid tbody > tr:nth-of-type({{row}}) .action-menu-item" parameterized="true" timeout="30"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml index f54fbf4cf4d53..55daae7692040 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="OrdersGridSection"> <element name="spinner" type="button" selector=".spinner"/> <element name="gridLoadingMask" type="button" selector=".admin__data-grid-loading-mask"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 317a325693c65..05f20371851bc 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateInvoiceTest"> <annotations> <features value="Sales"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml new file mode 100644 index 0000000000000..0b737ed459f3b --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateOrderWithMinimumAmountEnabledTest"> + <annotations> + <features value="Sales"/> + <stories value="Admin create order"/> + <title value="Admin order is not restricted by 'Minimum Order Amount' configuration."/> + <description value="Admin should be able to create an order regardless of the minimum order amount."/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-92925"/> + <group value="sales"/> + </annotations> + <before> + <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> + <createData entity="EnabledMinimumOrderAmount500" stepKey="enableMinimumOrderAmount"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="ClearCacheActionGroup" stepKey="clearCacheBefore"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <createData entity="DisabledMinimumOrderAmount" stepKey="disableMinimumOrderAmount"/> + <actionGroup ref="ClearCacheActionGroup" stepKey="clearCacheAfter"/> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="amOnLogoutPage"/> + </after> + + <!--Admin creates order--> + <comment userInput="Admin creates order" stepKey="adminCreateOrder"/> + <actionGroup ref="navigateToNewOrderPageNewCustomerSingleStore" stepKey="navigateToNewOrderPage"/> + + <actionGroup ref="addSimpleProductToOrder" stepKey="addSimpleProductToOrder"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + + <!--Fill customer group information--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectGroup"/> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillEmail"/> + + <!--Fill customer address information--> + <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping"/> + + <!--Verify totals on Order page--> + <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="${{AdminOrderSimpleProduct.subtotal}}" stepKey="seeOrderSubTotal"/> + <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="${{AdminOrderSimpleProduct.shipping}}" stepKey="seeOrderShipping"/> + <see selector="{{AdminOrderFormTotalSection.grandTotal}}" userInput="${{AdminOrderSimpleProduct.grandTotal}}" stepKey="seeCorrectGrandTotal"/> + + <!--Submit Order and verify information--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage"/> + + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" stepKey="seeOrderPendingStatus"/> + <grabTextFrom selector="|Order # (\d+)|" stepKey="orderId"/> + <assertNotEmpty actual="$orderId" stepKey="assertOrderIdIsNotEmpty"/> + <actionGroup ref="verifyBasicOrderInformation" stepKey="verifyOrderInformation"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="shippingAddress" value="US_Address_TX"/> + <argument name="billingAddress" value="US_Address_TX"/> + </actionGroup> + <actionGroup ref="seeProductInItemsOrdered" stepKey="seeSimpleProductInItemsOrdered"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml index 23e4ae676b35b..cc69b6dfb7d41 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml @@ -6,15 +6,16 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminSubmitsOrderWithAndWithoutEmailTest"> <annotations> + <features value="Sales"/> + <stories value="Create orders"/> <title value="Email is required to create an order from Admin Panel"/> <description value="Admin should not be able to submit orders without an email address"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-92980"/> <group value="sales"/> - </annotations> <before> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml index 26139f7182bab..60df3f27fd65b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml @@ -7,10 +7,11 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="CreditMemoTotalAfterShippingDiscountTest"> <annotations> - <features value="Credit memo"/> + <features value="Sales"/> + <stories value="Credit memos"/> <title value="Verify credit memo grand total after shipping discount is applied via Cart Price Rule"/> <description value="Verify credit memo grand total after shipping discount is applied via Cart Price Rule"/> <severity value="MAJOR"/> @@ -23,6 +24,7 @@ <requiredEntity createDataKey="createCategory"/> </createData> <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <actionGroup ref="SetTaxClassForShipping" stepKey="setShippingTaxClass"/> </before> <after> @@ -30,6 +32,7 @@ <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteSalesRule"> <argument name="ruleName" value="{{ApiSalesRule.name}}"/> </actionGroup> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> <deleteData createDataKey="createCategory" stepKey="deleteProduct1"/> <deleteData createDataKey="createProduct" stepKey="deleteCategory1"/> @@ -42,19 +45,15 @@ <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ApiSalesRule.name}}" stepKey="fillRuleName"/> <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsite"/> <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="chooseNotLoggedInCustomerGroup"/> - <!--<selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="NOT LOGGED IN" stepKey="selectCustomerGroup"/>--> <!-- Open the Actions Tab in the Rules Edit page --> <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.applyDiscountToShippingLabel}}" stepKey="waitForElementToBeVisible"/> <click selector="{{AdminCartPriceRulesFormSection.applyDiscountToShippingLabel}}" stepKey="enableApplyDiscountToShiping"/> - <seeCheckboxIsChecked selector="{{AdminCartPriceRulesFormSection.applyDiscountToShipping}}" stepKey="DiscountIsAppliedToShiping"/> + <seeCheckboxIsChecked selector="{{AdminCartPriceRulesFormSection.applyDiscountToShipping}}" stepKey="discountIsAppliedToShiping"/> <selectOption selector="{{AdminCartPriceRulesFormSection.apply}}" userInput="Fixed amount discount" stepKey="selectActionType"/> <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="10" stepKey="fillDiscountAmount"/> - <!--<scrollTo selector="{{AdminCartPriceRulesFormSection.applyDiscountToShippingLabel}}" stepKey="scrollToShippingLabel"/>--> - <!--<click selector="{{AdminCartPriceRulesFormSection.applyDiscountToShippingLabel}}" stepKey="enableApplyDiscountToShiping"/>--> - <!--<seeCheckboxIsChecked selector="{{AdminCartPriceRulesFormSection.applyDiscountToShipping}}" stepKey="DiscountIsAppliedToShiping"/>--> <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> @@ -91,7 +90,8 @@ <!-- Search for Order in the order grid --> <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/> + <waitForPageLoad time="30" stepKey="waitForOrderListPageLoad"/> + <conditionalClick selector="{{AdminOrdersGridSection.clearFilters}}" dependentSelector="{{AdminOrdersGridSection.clearFilters}}" visible="true" stepKey="clearExistingOrderFilter"/> <fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="searchOrderNum"/> <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearch"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/> @@ -131,6 +131,6 @@ <assertEquals expected='-$15.00' expectedType="string" actual="($grabRefundDiscountValue)" message="notExpectedDiscountOnRefundPage" stepKey="assertDiscountValue1"/> <grabTextFrom selector="{{AdminInvoiceTotalSection.grandTotal}}" stepKey="grabRefundGrandTotal"/> <assertEquals expected="($grabInvoiceGrandTotal)" actual="($grabRefundGrandTotal)" message="RefundGrandTotalMatchesWithInvoiceGrandTotal" stepKey="compareRefundGrandTotalAndInvoiceGrandTotal"/> - </test> + </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml index 4dc3c7a6ae788..0fdd8d8c35b32 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <before> <!--Create order via API--> diff --git a/app/code/Magento/Sales/Test/Mftf/composer.json b/app/code/Magento/Sales/Test/Mftf/composer.json deleted file mode 100644 index ac26bb1bf49f5..0000000000000 --- a/app/code/Magento/Sales/Test/Mftf/composer.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "magento/functional-test-module-sales", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-sales-sequence": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-sales-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/FormTest.php b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/FormTest.php new file mode 100644 index 0000000000000..4cbd30b1392fa --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Create/FormTest.php @@ -0,0 +1,197 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Block\Adminhtml\Order\Create; + +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Model\Session\Quote as QuoteSession; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\Address\Mapper; +use Magento\Customer\Model\Metadata\FormFactory; +use Magento\Framework\Currency; +use Magento\Framework\Json\EncoderInterface; +use Magento\Framework\Locale\CurrencyInterface; +use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\Quote\Payment; +use Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Sales\Model\AdminOrder\Create; +use Magento\Store\Model\Store; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class FormTest extends TestCase +{ + /** + * @var QuoteSession|MockObject + */ + private $quoteSession; + + /** + * @var CustomerRepositoryInterface|MockObject + */ + private $customerRepository; + + /** + * @var CurrencyInterface|MockObject + */ + private $localeCurrency; + + /** + * @var Form + */ + private $block; + + /** + * @inheritdoc + */ + protected function setUp() + { + /** @var Context|MockObject $context */ + $context = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->quoteSession = $this->getMockBuilder(QuoteSession::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerId', 'getQuoteId', 'getStoreId', 'getStore', 'getQuote']) + ->getMock(); + /** @var Create|MockObject $create */ + $create = $this->getMockBuilder(Create::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var PriceCurrencyInterface|MockObject $priceCurrency */ + $priceCurrency = $this->getMockForAbstractClass(PriceCurrencyInterface::class); + /** @var EncoderInterface|MockObject $encoder */ + $encoder = $this->getMockForAbstractClass(EncoderInterface::class); + $encoder->method('encode') + ->willReturnCallback(function ($param) { + return json_encode($param); + }); + /** @var FormFactory|MockObject $formFactory */ + $formFactory = $this->getMockBuilder(FormFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); + + $this->localeCurrency = $this->getMockForAbstractClass(CurrencyInterface::class); + /** @var Mapper|MockObject $addressMapper */ + $addressMapper = $this->getMockBuilder(Mapper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->block = new Form( + $context, + $this->quoteSession, + $create, + $priceCurrency, + $encoder, + $formFactory, + $this->customerRepository, + $this->localeCurrency, + $addressMapper + ); + } + + /** + * Checks if order contains all needed data. + */ + public function testGetOrderDataJson() + { + $customerId = 1; + $storeId = 1; + $quoteId = 2; + $expected = [ + 'customer_id' => $customerId, + 'addresses' => [], + 'store_id' => $storeId, + 'currency_symbol' => '$', + 'shipping_method_reseted' => false, + 'payment_method' => 'free', + 'quote_id' => $quoteId + ]; + + $this->quoteSession->method('getCustomerId') + ->willReturn($customerId); + $this->quoteSession->method('getStoreId') + ->willReturn($storeId); + $this->quoteSession->method('getQuoteId') + ->willReturn($quoteId); + + $customer = $this->getMockBuilder(CustomerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $customer->method('getAddresses') + ->willReturn([]); + $this->customerRepository->method('getById') + ->with($customerId) + ->willReturn($customer); + + $this->withCurrencySymbol('$'); + + $this->withQuote(); + + self::assertEquals($expected, json_decode($this->block->getOrderDataJson(), true)); + } + + /** + * Configures mock object for currency. + * + * @param string $symbol + */ + private function withCurrencySymbol(string $symbol) + { + $store = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $store->method('getCurrentCurrencyCode') + ->willReturn('USD'); + $this->quoteSession->method('getStore') + ->willReturn($store); + + $currency = $this->getMockBuilder(Currency::class) + ->disableOriginalConstructor() + ->getMock(); + $currency->method('getSymbol') + ->willReturn($symbol); + $this->localeCurrency->method('getCurrency') + ->with('USD') + ->willReturn($currency); + } + + /** + * Configures shipping and payment mock objects. + */ + private function withQuote() + { + $quote = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + $this->quoteSession->method('getQuote') + ->willReturn($quote); + + $address = $this->getMockBuilder(Address::class) + ->disableOriginalConstructor() + ->getMock(); + $address->method('getShippingMethod') + ->willReturn('free'); + $quote->method('getShippingAddress') + ->willReturn($address); + + $payment = $this->getMockBuilder(Payment::class) + ->disableOriginalConstructor() + ->getMock(); + $payment->method('getMethod') + ->willReturn('free'); + $quote->method('getPayment') + ->willReturn($payment); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Block/Order/Create/TotalsTest.php b/app/code/Magento/Sales/Test/Unit/Block/Order/Create/TotalsTest.php index 2a839dd018dba..492cfee5f5d83 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Order/Create/TotalsTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Order/Create/TotalsTest.php @@ -70,10 +70,10 @@ protected function setUp() $this->quoteMock->expects($this->any()) ->method('getBillingAddress') - ->willreturn($this->billingAddressMock); + ->willReturn($this->billingAddressMock); $this->quoteMock->expects($this->any()) ->method('getShippingAddress') - ->willreturn($this->shippingAddressMock); + ->willReturn($this->shippingAddressMock); $this->sessionQuoteMock->expects($this->any())->method('getQuote')->willReturn($this->quoteMock); $this->totals = $this->helperManager->getObject( \Magento\Sales\Block\Adminhtml\Order\Create\Totals::class, @@ -88,7 +88,7 @@ public function testGetTotals($isVirtual) { $expected = 'expected'; $this->quoteMock->expects($this->at(1))->method('collectTotals'); - $this->quoteMock->expects($this->once())->method('isVirtual')->willreturn($isVirtual); + $this->quoteMock->expects($this->once())->method('isVirtual')->willReturn($isVirtual); if ($isVirtual) { $this->billingAddressMock->expects($this->once())->method('getTotals')->willReturn($expected); } else { diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/EmailTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/EmailTest.php index c6f1be2a88540..910164f029f1c 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/EmailTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Creditmemo/AbstractCreditmemo/EmailTest.php @@ -73,6 +73,9 @@ class EmailTest extends \PHPUnit\Framework\TestCase */ protected $resultRedirectMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -98,7 +101,10 @@ protected function setUp() \Magento\Framework\ObjectManager\ObjectManager::class, ['create'] ); - $this->messageManager = $this->createPartialMock(\Magento\Framework\Message\Manager::class, ['addSuccess']); + $this->messageManager = $this->createPartialMock( + \Magento\Framework\Message\Manager::class, + ['addSuccessMessage'] + ); $this->session = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); $this->actionFlag = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get']); $this->helper = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); @@ -128,6 +134,9 @@ protected function setUp() ); } + /** + * testEmail + */ public function testEmail() { $cmId = 10000031; @@ -147,7 +156,7 @@ public function testEmail() ->method('notify') ->willReturn(true); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You sent the message.'); $this->assertInstanceOf( @@ -157,6 +166,9 @@ public function testEmail() $this->assertEquals($this->response, $this->creditmemoEmail->getResponse()); } + /** + * testEmailNoCreditmemoId + */ public function testEmailNoCreditmemoId() { $this->request->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Invoice/AbstractInvoice/EmailTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Invoice/AbstractInvoice/EmailTest.php index f71d98f94a82a..8407cc251db93 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Invoice/AbstractInvoice/EmailTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Invoice/AbstractInvoice/EmailTest.php @@ -89,6 +89,9 @@ class EmailTest extends \PHPUnit\Framework\TestCase */ protected $invoiceManagement; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -153,6 +156,9 @@ protected function setUp() ); } + /** + * testEmail + */ public function testEmail() { $invoiceId = 10000031; @@ -196,7 +202,7 @@ public function testEmail() ->with($invoiceId) ->willReturn(true); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You sent the message.'); $this->resultRedirectFactory->expects($this->atLeastOnce()) @@ -209,6 +215,9 @@ public function testEmail() $this->assertInstanceOf(\Magento\Backend\Model\View\Result\Redirect::class, $this->invoiceEmail->execute()); } + /** + * testEmailNoInvoiceId + */ public function testEmailNoInvoiceId() { $this->request->expects($this->once()) @@ -226,6 +235,9 @@ public function testEmailNoInvoiceId() $this->assertInstanceOf(\Magento\Backend\Model\View\Result\Forward::class, $this->invoiceEmail->execute()); } + /** + * testEmailNoInvoice + */ public function testEmailNoInvoice() { $invoiceId = 10000031; diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/CancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/CancelTest.php index 363f78e738f12..a368bf779398f 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/CancelTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/CancelTest.php @@ -61,6 +61,9 @@ class CancelTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -77,7 +80,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) ->disableOriginalConstructor() @@ -108,6 +111,9 @@ protected function setUp() ); } + /** + * testExecuteNotPost + */ public function testExecuteNotPost() { $this->validatorMock->expects($this->once()) @@ -117,7 +123,7 @@ public function testExecuteNotPost() ->method('isPost') ->willReturn(false); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('You have not canceled the item.'); $this->resultRedirect->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ProcessDataTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ProcessDataTest.php index e818b1edbc1d7..3e22867ce1f65 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ProcessDataTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ProcessDataTest.php @@ -63,6 +63,9 @@ class ProcessDataTest extends \PHPUnit\Framework\TestCase */ protected $resultForwardFactory; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -226,7 +229,11 @@ public function testExecute($noDiscount, $couponCode, $errorMessage, $actualCoup ); $this->escaper->expects($this->once())->method('escapeHtml')->with($couponCode)->willReturn($couponCode); - $this->messageManager->expects($this->once())->method('addError')->with($errorMessageManager)->willReturnSelf(); + $this->messageManager + ->expects($this->once()) + ->method('addErrorMessage') + ->with($errorMessageManager) + ->willReturnSelf(); $this->resultForward->expects($this->once()) ->method('forward') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php index 1fdd9759f5045..b11d73de736d4 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php @@ -108,6 +108,9 @@ class ReorderTest extends \PHPUnit\Framework\TestCase */ private $orderId; + /** + * @return void + */ protected function setUp() { $this->orderId = 111; @@ -118,7 +121,9 @@ protected function setUp() ->getMock(); $this->requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass(); $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)->getMockForAbstractClass(); - $this->resultForwardFactoryMock = $this->getMockBuilder(ForwardFactory::class)->getMock(); + $this->resultForwardFactoryMock = $this->getMockBuilder(ForwardFactory::class) + ->disableOriginalConstructor() + ->getMock(); $this->resultRedirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) ->disableOriginalConstructor() ->getMock(); @@ -159,6 +164,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testExecuteForward() { $this->clearStorage(); @@ -169,6 +177,9 @@ public function testExecuteForward() $this->assertInstanceOf(Forward::class, $this->reorder->execute()); } + /** + * @return void + */ public function testExecuteRedirectOrderGrid() { $this->clearStorage(); @@ -181,6 +192,9 @@ public function testExecuteRedirectOrderGrid() $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); } + /** + * @return void + */ public function testExecuteRedirectBack() { $this->clearStorage(); @@ -195,6 +209,9 @@ public function testExecuteRedirectBack() $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); } + /** + * @return void + */ public function testExecuteRedirectNewOrder() { $this->clearStorage(); @@ -209,6 +226,9 @@ public function testExecuteRedirectNewOrder() $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); } + /** + * @return void + */ private function clearStorage() { $this->objectManagerMock->expects($this->at(0)) @@ -218,6 +238,9 @@ private function clearStorage() $this->quoteSessionMock->expects($this->once())->method('clearStorage')->will($this->returnSelf()); } + /** + * @return void + */ private function getOrder() { $this->requestMock->expects($this->once()) @@ -235,20 +258,26 @@ private function getOrder() */ private function canReorder($result) { - $entity_id = 1; - $this->orderMock->expects($this->once())->method('getEntityId')->willReturn($entity_id); + $entityId = 1; + $this->orderMock->expects($this->once())->method('getEntityId')->willReturn($entityId); $this->reorderHelperMock->expects($this->once()) ->method('canReorder') - ->with($entity_id) + ->with($entityId) ->willReturn($result); } + /** + * @return void + */ private function prepareForward() { $this->resultForwardFactoryMock->expects($this->once())->method('create')->willReturn($this->resultForwardMock); $this->resultForwardMock->expects($this->once())->method('forward')->with('noroute')->willReturnSelf(); } + /** + * @return void + */ private function createRedirect() { $this->resultRedirectFactoryMock->expects($this->once()) @@ -284,6 +313,9 @@ private function getUnavailableProducts(array $unavailableProducts) ->willReturn($unavailableProducts); } + /** + * @return void + */ private function initFromOrder() { $this->orderMock->expects($this->once())->method('setReordered')->with(true)->willReturnSelf(); diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/CancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/CancelTest.php index af218903a3868..3e9e6af0b4181 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/CancelTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/CancelTest.php @@ -292,7 +292,7 @@ public function testExecute() ->method('cancel') ->with($creditmemoId); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The credit memo has been canceled.'); $this->resultRedirectFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/PrintActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/PrintActionTest.php index 881af16f5fe69..11ccdd87a5664 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/PrintActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/PrintActionTest.php @@ -81,6 +81,9 @@ class PrintActionTest extends \PHPUnit\Framework\TestCase */ protected $resultForwardMock; + /** + * test setup + */ protected function setUp() { $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) @@ -155,7 +158,8 @@ public function testExecute() $creditmemoId = 2; $date = '2015-01-19_13-03-45'; $fileName = 'creditmemo2015-01-19_13-03-45.pdf'; - $fileContents = 'pdf0123456789'; + $pdfContent = 'pdf0123456789'; + $fileData = ['type' => 'string', 'value' => $pdfContent, 'rm' => true]; $this->prepareTestExecute($creditmemoId); $this->objectManagerMock->expects($this->any()) @@ -184,12 +188,12 @@ public function testExecute() ->willReturn($date); $this->pdfMock->expects($this->once()) ->method('render') - ->willReturn($fileContents); + ->willReturn($pdfContent); $this->fileFactoryMock->expects($this->once()) ->method('create') ->with( $fileName, - $fileContents, + $fileData, \Magento\Framework\App\Filesystem\DirectoryList::VAR_DIR, 'application/pdf' ) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php index 490203563bb36..0112d09eb735f 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php @@ -235,7 +235,7 @@ public function testSaveActionWithNegativeCreditmemo() */ protected function _setSaveActionExpectationForMageCoreException($data, $errorMessage) { - $this->_messageManager->expects($this->once())->method('addError')->with($this->equalTo($errorMessage)); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($this->equalTo($errorMessage)); $this->_sessionMock->expects($this->once())->method('setFormData')->with($this->equalTo($data)); } } diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/VoidActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/VoidActionTest.php index 7e0163002b437..04adb63bc88ac 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/VoidActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/VoidActionTest.php @@ -335,7 +335,7 @@ public function testExecute() ->method('getInvoice') ->willReturn($invoiceMock); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You voided the credit memo.'); $this->resultRedirectFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/EmailTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/EmailTest.php index bb826be85850c..e9a0e573e3f9a 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/EmailTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/EmailTest.php @@ -87,6 +87,9 @@ class EmailTest extends \PHPUnit\Framework\TestCase */ protected $orderMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -119,7 +122,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->orderMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderInterface::class) @@ -152,6 +155,9 @@ protected function setUp() ); } + /** + * testEmail + */ public function testEmail() { $orderId = 10000031; @@ -171,7 +177,7 @@ public function testEmail() ->with($orderId) ->willReturn(true); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You sent the order email.'); $this->resultRedirect->expects($this->once()) ->method('setPath') @@ -185,6 +191,9 @@ public function testEmail() $this->assertEquals($this->response, $this->orderEmail->getResponse()); } + /** + * testEmailNoOrderId + */ public function testEmailNoOrderId() { $this->request->expects($this->once()) @@ -200,7 +209,7 @@ public function testEmailNoOrderId() ) ); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('This order no longer exists.'); $this->actionFlag->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/HoldTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/HoldTest.php index 68ce75781db78..30e25605f0b94 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/HoldTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/HoldTest.php @@ -61,6 +61,9 @@ class HoldTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -77,7 +80,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) ->disableOriginalConstructor() @@ -108,6 +111,9 @@ protected function setUp() ); } + /** + * testExecuteNotPost + */ public function testExecuteNotPost() { $this->validatorMock->expects($this->once()) @@ -117,7 +123,7 @@ public function testExecuteNotPost() ->method('isPost') ->willReturn(false); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('You have not put the order on hold.'); $this->resultRedirect->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CancelTest.php index 83b897656d185..667b6775384cf 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CancelTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CancelTest.php @@ -213,7 +213,7 @@ public function testExecute() ->method('save'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You canceled the invoice.'); $this->invoiceRepository->expects($this->once()) @@ -295,7 +295,7 @@ public function testExecuteModelException() ->will($this->throwException($e)); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($message); $invoiceMock->expects($this->once()) @@ -343,7 +343,7 @@ public function testExecuteException() ->will($this->throwException($e)); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($message); $invoiceMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CaptureTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CaptureTest.php index 0fabc24dd4421..fd53d91b9d9ea 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CaptureTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/CaptureTest.php @@ -223,7 +223,7 @@ public function testExecute() ->method('save'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The invoice has been captured.'); $invoiceMock->expects($this->once()) @@ -304,7 +304,7 @@ public function testExecuteModelException() ->getMock(); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($message); $invoiceMock->expects($this->once()) @@ -355,7 +355,7 @@ public function testExecuteException() ->getMock(); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($message); $invoiceMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/SaveTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/SaveTest.php index 3ffa0971770b1..17dc3f42a2fe2 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/SaveTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/SaveTest.php @@ -122,7 +122,7 @@ public function testExecuteNotValidPost() ->method('isPost') ->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with("The invoice can't be saved at this time. Please try again later."); $redirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/VoidActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/VoidActionTest.php index d7766d79e9c30..0fbff061650f8 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/VoidActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/VoidActionTest.php @@ -250,7 +250,7 @@ public function testExecute() ->will($this->returnValue($transactionMock)); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('The invoice has been voided.'); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) @@ -283,9 +283,9 @@ public function testExecuteNoInvoice() ->willReturn(null); $this->messageManagerMock->expects($this->never()) - ->method('addError'); + ->method('addErrorMessage'); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $resultForward = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Forward::class) ->disableOriginalConstructor() @@ -334,7 +334,7 @@ public function testExecuteModelException() ->willReturn($invoiceMock); $this->messageManagerMock->expects($this->once()) - ->method('addError'); + ->method('addErrorMessage'); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php index 756bade3c83c9..38449c5686631 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php @@ -90,6 +90,9 @@ class MassCancelTest extends \PHPUnit\Framework\TestCase */ private $orderManagementMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -201,11 +204,11 @@ public function testExecuteCanCancelOneOrder() $this->orderManagementMock->expects($this->at(1))->method('cancel')->with($order2id)->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('1 order(s) cannot be canceled.'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('We canceled 1 order(s).'); $this->resultRedirectMock->expects($this->once()) @@ -251,7 +254,7 @@ public function testExcludedCannotCancelOrders() $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('You cannot cancel the order(s).'); $this->resultRedirectMock->expects($this->once()) @@ -283,7 +286,7 @@ public function testException() $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willThrowException($exception); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Can not cancel'); $this->massAction->execute(); diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php index 02ff208445596..bfd1d1e825f89 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php @@ -90,6 +90,9 @@ class MassHoldTest extends \PHPUnit\Framework\TestCase */ protected $orderManagementMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -166,6 +169,9 @@ protected function setUp() ); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecuteOneOrderPutOnHold() { $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) @@ -196,11 +202,11 @@ public function testExecuteOneOrderPutOnHold() ->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('1 order(s) were not put on hold.'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('You have put 1 order(s) on hold.'); $this->resultRedirectMock->expects($this->once()) @@ -211,6 +217,9 @@ public function testExecuteOneOrderPutOnHold() $this->massAction->execute(); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecuteNoOrdersPutOnHold() { $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) @@ -240,7 +249,7 @@ public function testExecuteNoOrdersPutOnHold() ->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('No order(s) were put on hold.'); $this->resultRedirectMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php index cddb503925987..56473ec948f27 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php @@ -90,6 +90,9 @@ class MassUnholdTest extends \PHPUnit\Framework\TestCase */ private $orderManagementMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -164,6 +167,9 @@ protected function setUp() ); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecuteOneOrdersReleasedFromHold() { $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) @@ -196,11 +202,11 @@ public function testExecuteOneOrdersReleasedFromHold() $this->orderManagementMock->expects($this->atLeastOnce())->method('unHold')->willReturn(true); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('1 order(s) were not released from on hold status.'); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with('1 order(s) have been released from on hold status.'); $this->resultRedirectMock->expects($this->once()) @@ -211,6 +217,9 @@ public function testExecuteOneOrdersReleasedFromHold() $this->massAction->execute(); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecuteNoReleasedOrderFromHold() { $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) @@ -239,7 +248,7 @@ public function testExecuteNoReleasedOrderFromHold() ->willReturn(false); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('No order(s) were released from on hold status.'); $this->resultRedirectMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ReviewPaymentTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ReviewPaymentTest.php index 27bbbd4eb0043..452bde7023f9d 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ReviewPaymentTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ReviewPaymentTest.php @@ -51,6 +51,9 @@ class ReviewPaymentTest extends \PHPUnit\Framework\TestCase */ protected $loggerMock; + /** + * Test setup + */ protected function setUp() { $this->contextMock = $this->createPartialMock(\Magento\Backend\App\Action\Context::class, [ @@ -75,7 +78,7 @@ protected function setUp() ->getMockForAbstractClass(); $this->messageManagerMock = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->resultRedirectFactoryMock = $this->createPartialMock( @@ -112,6 +115,9 @@ protected function setUp() ); } + /** + * testExecuteUpdateAction + */ public function testExecuteUpdateAction() { $orderId = 30; @@ -137,7 +143,7 @@ public function testExecuteUpdateAction() $this->paymentMock->expects($this->once())->method('update'); $this->paymentMock->expects($this->any())->method('getIsTransactionApproved')->willReturn(true); - $this->messageManagerMock->expects($this->once())->method('addSuccess'); + $this->messageManagerMock->expects($this->once())->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/UnholdTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/UnholdTest.php index 7720b7615c3fa..cc4720f1c6b44 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/UnholdTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/UnholdTest.php @@ -61,6 +61,9 @@ class UnholdTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -77,7 +80,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->orderRepositoryMock = $this->getMockBuilder(\Magento\Sales\Api\OrderRepositoryInterface::class) ->disableOriginalConstructor() @@ -108,6 +111,9 @@ protected function setUp() ); } + /** + * testExecuteNotPost + */ public function testExecuteNotPost() { $this->validatorMock->expects($this->once()) @@ -117,7 +123,7 @@ public function testExecuteNotPost() ->method('isPost') ->willReturn(false); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Can\'t unhold order.'); $this->resultRedirect->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php index 97b3fe630aa52..7d39aca35d2c1 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/ViewTest.php @@ -98,6 +98,9 @@ class ViewTest extends \PHPUnit\Framework\TestCase */ protected $orderRepositoryMock; + /** + * Test setup + */ protected function setUp() { $this->orderManagementMock = $this->getMockBuilder(\Magento\Sales\Api\OrderManagementInterface::class) @@ -251,7 +254,7 @@ public function testGlobalException() ->method('critical') ->with($exception); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Exception occurred during order load') ->willReturnSelf(); $this->setPath('sales/order/index'); @@ -262,6 +265,9 @@ public function testGlobalException() ); } + /** + * initOrder + */ protected function initOrder() { $orderIdParam = 111; @@ -289,10 +295,13 @@ protected function initOrderSuccess() ); } + /** + * initOrderFail + */ protected function initOrderFail() { $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('This order no longer exists.') ->willReturnSelf(); $this->actionFlagMock->expects($this->once()) @@ -300,6 +309,9 @@ protected function initOrderFail() ->with('', \Magento\Sales\Controller\Adminhtml\Order::FLAG_NO_DISPATCH, true); } + /** + * initAction + */ protected function initAction() { $this->resultPageFactoryMock->expects($this->once()) @@ -318,6 +330,9 @@ protected function initAction() ->willReturnSelf(); } + /** + * prepareRedirect + */ protected function prepareRedirect() { $this->resultRedirectFactoryMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/PdfDocumentsMassActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/PdfDocumentsMassActionTest.php index 0841e239429a5..bb9662b7e02d8 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/PdfDocumentsMassActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/PdfDocumentsMassActionTest.php @@ -40,13 +40,16 @@ class PdfDocumentsMassActionTest extends \PHPUnit\Framework\TestCase */ private $filterMock; + /** + * Test setup + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->orderCollectionMock = $this->createMock(\Magento\Sales\Model\ResourceModel\Order\Collection::class); @@ -80,6 +83,9 @@ protected function setUp() ); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecute() { $exception = new \Exception(); @@ -88,7 +94,7 @@ public function testExecute() ->method('getCollection') ->with($this->orderCollectionMock) ->willThrowException($exception); - $this->messageManager->expects($this->once())->method('addError'); + $this->messageManager->expects($this->once())->method('addErrorMessage'); $this->resultRedirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->controller->execute($exception); diff --git a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php index ccc0bfe309f0c..37f0f754c7074 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/AdminOrder/EmailSenderTest.php @@ -34,6 +34,9 @@ class EmailSenderTest extends \PHPUnit\Framework\TestCase */ protected $orderSenderMock; + /** + * Test setup + */ protected function setUp() { $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); @@ -44,6 +47,9 @@ protected function setUp() $this->emailSender = new EmailSender($this->messageManagerMock, $this->loggerMock, $this->orderSenderMock); } + /** + * testSendSuccess + */ public function testSendSuccess() { $this->orderSenderMock->expects($this->once()) @@ -51,13 +57,16 @@ public function testSendSuccess() $this->assertTrue($this->emailSender->send($this->orderMock)); } + /** + * testSendFailure + */ public function testSendFailure() { $this->orderSenderMock->expects($this->once()) ->method('send') ->willThrowException(new \Magento\Framework\Exception\MailException(__('test message'))); $this->messageManagerMock->expects($this->once()) - ->method('addWarning'); + ->method('addWarningMessage'); $this->loggerMock->expects($this->once()) ->method('critical'); diff --git a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php index 56ec763a31cfe..e26b6e52b8d17 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/EmailSenderHandlerTest.php @@ -118,7 +118,7 @@ public function testExecute($configValue, $collectionItems, $emailSendingResult) $path = 'sales_email/general/async_sending'; $this->globalConfig - ->expects($this->once()) + ->expects($this->at(0)) ->method('getValue') ->with($path) ->willReturn($configValue); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ConfigTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ConfigTest.php index 86419c0c905b6..feee2816b2cd4 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ConfigTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ConfigTest.php @@ -38,6 +38,9 @@ class ConfigTest extends \PHPUnit\Framework\TestCase */ protected $storeManagerMock; + /** + * @return void + */ protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -47,6 +50,7 @@ protected function setUp() 'storeManager' => $this->storeManagerMock, ]); $this->statusFactoryMock = $this->getMockBuilder(\Magento\Sales\Model\Order\StatusFactory::class) + ->disableOriginalConstructor() ->setMethods(['load', 'create']) ->getMock(); $this->orderStatusCollectionFactoryMock = $this->createPartialMock( @@ -63,6 +67,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testGetInvisibleOnFrontStatuses() { $statuses = [ @@ -109,6 +116,9 @@ public function testGetInvisibleOnFrontStatuses() $this->assertSame($expectedResult, $result); } + /** + * @return void + */ public function testGetStateLabelByStateAndStatus() { $statuses = [ diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/Transaction/RepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/Transaction/RepositoryTest.php index 95966b138097c..e9bda29900858 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/Transaction/RepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Payment/Transaction/RepositoryTest.php @@ -122,14 +122,20 @@ protected function setUp() ); } - public function testCreate() + /** + * @return void + */ + public function testCreate(): void { $expected = "expect"; $this->metaData->expects($this->once())->method('getNewInstance')->willReturn($expected); $this->assertEquals($expected, $this->repository->create()); } - public function testSave() + /** + * @return void + */ + public function testSave(): void { $transactionId = 12; $transaction = $this->mockTransaction($transactionId); @@ -142,7 +148,10 @@ public function testSave() $this->assertSame($transaction, $this->repository->save($transaction)); } - public function testDelete() + /** + * @return void + */ + public function testDelete(): void { $transactionId = 12; $transaction = $this->mockTransaction($transactionId); @@ -152,7 +161,10 @@ public function testDelete() $this->assertTrue($this->repository->delete($transaction)); } - public function testGet() + /** + * @return void + */ + public function testGet(): void { $transactionId = 12; $transaction = $this->mockTransaction($transactionId); @@ -165,22 +177,24 @@ public function testGet() } /** + * @return void * @expectedException \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function testGetException() + public function testGetException(): void { $transactionId = null; $this->repository->get($transactionId); } /** + * @return void * @expectedException \Magento\Framework\Exception\NoSuchEntityException * @throws \Magento\Framework\Exception\InputException * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function testGetNoSuchEntity() + public function testGetNoSuchEntity(): void { $transactionId = null; $transactionIdFromArgument = 12; @@ -193,7 +207,10 @@ public function testGetNoSuchEntity() $this->assertSame($transaction, $this->repository->get(12)); } - public function testGetExistInStorage() + /** + * @return void + */ + public function testGetExistInStorage(): void { $transactionId = 12; $transaction = "transaction"; @@ -206,7 +223,10 @@ public function testGetExistInStorage() $this->assertSame($transaction, $this->repository->get($transactionId)); } - public function testGetList() + /** + * @return void + */ + public function testGetList(): void { $this->initListMock(); $this->collectionProcessor->expects($this->once()) @@ -215,7 +235,10 @@ public function testGetList() $this->assertSame($this->collection, $this->repository->getList($this->searchCriteria)); } - public function testGetByTransactionId() + /** + * @return void + */ + public function testGetByTransactionId(): void { $transactionId = "100-refund"; $paymentId = 1; @@ -241,7 +264,10 @@ public function testGetByTransactionId() $this->assertEquals($transaction, $this->repository->getByTransactionId($transactionId, $paymentId, $orderId)); } - public function testGetByTransactionIdNotFound() + /** + * @return void + */ + public function testGetByTransactionIdNotFound(): void { $transactionId = "100-refund"; $paymentId = 1; @@ -267,7 +293,10 @@ public function testGetByTransactionIdNotFound() ); } - public function testGetByTransactionIdFromStorage() + /** + * @return void + */ + public function testGetByTransactionIdFromStorage(): void { $transactionId = "100-refund"; $paymentId = 1; @@ -284,11 +313,13 @@ public function testGetByTransactionIdFromStorage() ); } - public function testGetByTransactionType() + /** + * @return void + */ + public function testGetByTransactionType(): void { $transactionType = Transaction::TYPE_AUTH; $paymentId = 1; - $orderId = 3; $cacheStorage = 'txn_type'; $identityFieldsForCache = [$transactionType, $paymentId]; $this->entityStorage->expects($this->once()) @@ -341,15 +372,17 @@ public function testGetByTransactionType() ->with($transaction, $identityFieldsForCache, $cacheStorage); $this->assertEquals( $transaction, - $this->repository->getByTransactionType($transactionType, $paymentId, $orderId) + $this->repository->getByTransactionType($transactionType, $paymentId) ); } - public function testGetByTransactionTypeFromCache() + /** + * @return void + */ + public function testGetByTransactionTypeFromCache(): void { $transactionType = Transaction::TYPE_AUTH; $paymentId = 1; - $orderId = 3; $cacheStorage = 'txn_type'; $transaction = "transaction"; $identityFieldsForCache = [$transactionType, $paymentId]; @@ -358,7 +391,7 @@ public function testGetByTransactionTypeFromCache() ->willReturn($transaction); $this->assertEquals( $transaction, - $this->repository->getByTransactionType($transactionType, $paymentId, $orderId) + $this->repository->getByTransactionType($transactionType, $paymentId) ); } @@ -379,7 +412,7 @@ protected function mockTransaction($transactionId, $withoutTransactionIdMatcher /** * @return void */ - protected function initListMock() + protected function initListMock(): void { $this->searchResultFactory->method('create')->willReturn($this->collection); $this->collection->expects($this->once())->method('addPaymentInformation')->with(['method']); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php index bf9b3a67f9640..f17d18c3be1da 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentDocumentFactoryTest.php @@ -16,6 +16,7 @@ use Magento\Sales\Model\Order\Shipment\TrackFactory; use Magento\Sales\Model\Order\Shipment\Track; use Magento\Framework\EntityManager\HydratorInterface; +use Magento\Sales\Model\Order\ShipmentDocumentFactory\ExtensionAttributesProcessor; /** * Class ShipmentDocumentFactoryTest @@ -68,6 +69,11 @@ class ShipmentDocumentFactoryTest extends \PHPUnit\Framework\TestCase */ private $hydratorMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ExtensionAttributesProcessor + */ + private $extensionAttributeProcessorMock; + /** * @var \PHPUnit_Framework_MockObject_MockObject|Track */ @@ -113,10 +119,15 @@ protected function setUp() ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->extensionAttributeProcessorMock = $this->getMockBuilder(ExtensionAttributesProcessor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->shipmentDocumentFactory = new ShipmentDocumentFactory( $this->shipmentFactoryMock, $this->hydratorPoolMock, - $this->trackFactoryMock + $this->trackFactoryMock, + $this->extensionAttributeProcessorMock ); } @@ -129,6 +140,7 @@ public function testCreate() $packages = []; $items = [1 => 10]; + $this->extensionAttributeProcessorMock->expects($this->never())->method('execute'); $this->itemMock->expects($this->once())->method('getOrderItemId')->willReturn(1); $this->itemMock->expects($this->once())->method('getQty')->willReturn(10); $this->itemMock->expects($this->once()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentTest.php index 5204073454345..a7649b5387cbe 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/ShipmentTest.php @@ -25,10 +25,13 @@ class ShipmentTest extends \PHPUnit\Framework\TestCase private $commentCollection; /** - * @var \Magento\Sales\Model\Order\shipment + * @var Shipment */ private $shipmentModel; + /** + * @return void + */ protected function setUp() { $helperManager = new ObjectManager($this); @@ -40,6 +43,11 @@ protected function setUp() ]); } + /** + * Test to Returns increment id + * + * @return void + */ public function testGetIncrementId() { $this->shipmentModel->setIncrementId('test_increment_id'); @@ -47,7 +55,10 @@ public function testGetIncrementId() } /** - * @covers \Magento\Sales\Model\Order\Shipment::getCommentsCollection + * Test to Retrieves comments collection + * + * @return void + * @throws \ReflectionException */ public function testGetCommentsCollection() { @@ -58,35 +69,34 @@ public function testGetCommentsCollection() ->disableOriginalConstructor() ->setMethods(['setShipment']) ->getMock(); - $shipmentItem->expects(static::once()) - ->method('setShipment') + $shipmentItem->method('setShipment') ->with($this->shipmentModel); $collection = [$shipmentItem]; - $this->commentCollection->expects(static::once()) + $this->commentCollection->expects(self::once()) ->method('setShipmentFilter') ->with($shipmentId) ->willReturnSelf(); - $this->commentCollection->expects(static::once()) + $this->commentCollection->expects(self::once()) ->method('setCreatedAtOrder') ->willReturnSelf(); - $this->commentCollection->expects(static::once()) - ->method('load') - ->willReturnSelf(); - $reflection = new \ReflectionClass(Collection::class); $reflectionProperty = $reflection->getProperty('_items'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($this->commentCollection, $collection); - $expected = $this->shipmentModel->getCommentsCollection(); + $actual = $this->shipmentModel->getCommentsCollection(); - static::assertEquals($expected, $this->commentCollection); + self::assertTrue(is_object($actual)); + self::assertEquals($this->commentCollection, $actual); } /** - * @covers \Magento\Sales\Model\Order\Shipment::getComments + * Test to Returns comments + * + * @return void + * @throws \ReflectionException */ public function testGetComments() { @@ -97,30 +107,27 @@ public function testGetComments() ->disableOriginalConstructor() ->setMethods(['setShipment']) ->getMock(); - $shipmentItem->expects(static::once()) + $shipmentItem->expects(self::once()) ->method('setShipment') ->with($this->shipmentModel); $collection = [$shipmentItem]; - $this->commentCollection->expects(static::once()) - ->method('setShipmentFilter') + $this->commentCollection->method('setShipmentFilter') ->with($shipmentId) ->willReturnSelf(); - $this->commentCollection->expects(static::once()) - ->method('load') - ->willReturnSelf(); - $reflection = new \ReflectionClass(Collection::class); $reflectionProperty = $reflection->getProperty('_items'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($this->commentCollection, $collection); - $this->commentCollection->expects(static::once()) + $this->commentCollection->expects(self::once()) ->method('getItems') ->willReturn($collection); - static::assertEquals($this->shipmentModel->getComments(), $collection); + $actual = $this->shipmentModel->getComments(); + self::assertTrue(is_array($actual)); + self::assertEquals($collection, $actual); } /** @@ -131,7 +138,7 @@ private function initCommentsCollectionFactoryMock() { $this->commentCollection = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() - ->setMethods(['setShipmentFilter', 'setCreatedAtOrder', 'getItems', 'load', '__wakeup']) + ->setMethods(['setShipmentFilter', 'setCreatedAtOrder', 'getItems', 'load']) ->getMock(); $this->commentCollectionFactory = $this->getMockBuilder(CollectionFactory::class) @@ -139,8 +146,7 @@ private function initCommentsCollectionFactoryMock() ->setMethods(['create']) ->getMock(); - $this->commentCollectionFactory->expects(static::any()) - ->method('create') + $this->commentCollectionFactory->method('create') ->willReturn($this->commentCollection); } } diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index 9d6d11d56c81f..157420b3d0c73 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -132,6 +132,14 @@ <source_model>Magento\Config\Model\Config\Source\Enabledisable</source_model> <backend_model>Magento\Sales\Model\Config\Backend\Email\AsyncSending</backend_model> </field> + <field id="sending_limit" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Limit per cron run</label> + <comment>Limit how many entities (orders/shipments/etc) will be processed during one cron run.</comment> + <validate>required-number validate-number validate-greater-than-zero</validate> + <depends> + <field id="async_sending">1</field> + </depends> + </field> </group> <group id="order" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Order</label> @@ -393,7 +401,7 @@ </group> </section> <section id="rss"> - <group id="order" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> + <group id="order" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Order</label> <field id="status" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Customer Order Status Notification</label> @@ -402,7 +410,7 @@ </group> </section> <section id="dev"> - <group id="grid" type="text" sortOrder="131" showInDefault="1" showInWebsite="0" showInStore="0"> + <group id="grid" translate="label" type="text" sortOrder="131" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Grid Settings</label> <field id="async_indexing" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Asynchronous indexing</label> diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index da2408416133d..d4d10bfa6dcce 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -29,6 +29,7 @@ <sales_email> <general> <async_sending>0</async_sending> + <sending_limit>50</sending_limit> </general> <order> <enabled>1</enabled> diff --git a/app/code/Magento/Sales/etc/db_schema_whitelist.json b/app/code/Magento/Sales/etc/db_schema_whitelist.json index 415ee7f0b26c1..8790523c97c21 100644 --- a/app/code/Magento/Sales/etc/db_schema_whitelist.json +++ b/app/code/Magento/Sales/etc/db_schema_whitelist.json @@ -1,1248 +1,1248 @@ { - "sales_order": { - "column": { - "entity_id": true, - "state": true, - "status": true, - "coupon_code": true, - "protect_code": true, - "shipping_description": true, - "is_virtual": true, - "store_id": true, - "customer_id": true, - "base_discount_amount": true, - "base_discount_canceled": true, - "base_discount_invoiced": true, - "base_discount_refunded": true, - "base_grand_total": true, - "base_shipping_amount": true, - "base_shipping_canceled": true, - "base_shipping_invoiced": true, - "base_shipping_refunded": true, - "base_shipping_tax_amount": true, - "base_shipping_tax_refunded": true, - "base_subtotal": true, - "base_subtotal_canceled": true, - "base_subtotal_invoiced": true, - "base_subtotal_refunded": true, - "base_tax_amount": true, - "base_tax_canceled": true, - "base_tax_invoiced": true, - "base_tax_refunded": true, - "base_to_global_rate": true, - "base_to_order_rate": true, - "base_total_canceled": true, - "base_total_invoiced": true, - "base_total_invoiced_cost": true, - "base_total_offline_refunded": true, - "base_total_online_refunded": true, - "base_total_paid": true, - "base_total_qty_ordered": true, - "base_total_refunded": true, - "discount_amount": true, - "discount_canceled": true, - "discount_invoiced": true, - "discount_refunded": true, - "grand_total": true, - "shipping_amount": true, - "shipping_canceled": true, - "shipping_invoiced": true, - "shipping_refunded": true, - "shipping_tax_amount": true, - "shipping_tax_refunded": true, - "store_to_base_rate": true, - "store_to_order_rate": true, - "subtotal": true, - "subtotal_canceled": true, - "subtotal_invoiced": true, - "subtotal_refunded": true, - "tax_amount": true, - "tax_canceled": true, - "tax_invoiced": true, - "tax_refunded": true, - "total_canceled": true, - "total_invoiced": true, - "total_offline_refunded": true, - "total_online_refunded": true, - "total_paid": true, - "total_qty_ordered": true, - "total_refunded": true, - "can_ship_partially": true, - "can_ship_partially_item": true, - "customer_is_guest": true, - "customer_note_notify": true, - "billing_address_id": true, - "customer_group_id": true, - "edit_increment": true, - "email_sent": true, - "send_email": true, - "forced_shipment_with_invoice": true, - "payment_auth_expiration": true, - "quote_address_id": true, - "quote_id": true, - "shipping_address_id": true, - "adjustment_negative": true, - "adjustment_positive": true, - "base_adjustment_negative": true, - "base_adjustment_positive": true, - "base_shipping_discount_amount": true, - "base_subtotal_incl_tax": true, - "base_total_due": true, - "payment_authorization_amount": true, - "shipping_discount_amount": true, - "subtotal_incl_tax": true, - "total_due": true, - "weight": true, - "customer_dob": true, - "increment_id": true, - "applied_rule_ids": true, - "base_currency_code": true, - "customer_email": true, - "customer_firstname": true, - "customer_lastname": true, - "customer_middlename": true, - "customer_prefix": true, - "customer_suffix": true, - "customer_taxvat": true, - "discount_description": true, - "ext_customer_id": true, - "ext_order_id": true, - "global_currency_code": true, - "hold_before_state": true, - "hold_before_status": true, - "order_currency_code": true, - "original_increment_id": true, - "relation_child_id": true, - "relation_child_real_id": true, - "relation_parent_id": true, - "relation_parent_real_id": true, - "remote_ip": true, - "shipping_method": true, - "store_currency_code": true, - "store_name": true, - "x_forwarded_for": true, - "customer_note": true, - "created_at": true, - "updated_at": true, - "total_item_count": true, - "customer_gender": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "shipping_discount_tax_compensation_amount": true, - "base_shipping_discount_tax_compensation_amnt": true, - "discount_tax_compensation_invoiced": true, - "base_discount_tax_compensation_invoiced": true, - "discount_tax_compensation_refunded": true, - "base_discount_tax_compensation_refunded": true, - "shipping_incl_tax": true, - "base_shipping_incl_tax": true, - "coupon_rule_name": true - }, - "index": { - "SALES_ORDER_STATUS": true, - "SALES_ORDER_STATE": true, - "SALES_ORDER_STORE_ID": true, - "SALES_ORDER_CREATED_AT": true, - "SALES_ORDER_CUSTOMER_ID": true, - "SALES_ORDER_EXT_ORDER_ID": true, - "SALES_ORDER_QUOTE_ID": true, - "SALES_ORDER_UPDATED_AT": true, - "SALES_ORDER_SEND_EMAIL": true, - "SALES_ORDER_EMAIL_SENT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "SALES_ORDER_STORE_ID_STORE_STORE_ID": true, - "SALES_ORDER_INCREMENT_ID_STORE_ID": true - } - }, - "sales_order_grid": { - "column": { - "entity_id": true, - "status": true, - "store_id": true, - "store_name": true, - "customer_id": true, - "base_grand_total": true, - "base_total_paid": true, - "grand_total": true, - "total_paid": true, - "increment_id": true, - "base_currency_code": true, - "order_currency_code": true, - "shipping_name": true, - "billing_name": true, - "created_at": true, - "updated_at": true, - "billing_address": true, - "shipping_address": true, - "shipping_information": true, - "customer_email": true, - "customer_group": true, - "subtotal": true, - "shipping_and_handling": true, - "customer_name": true, - "payment_method": true, - "total_refunded": true - }, - "index": { - "SALES_ORDER_GRID_STATUS": true, - "SALES_ORDER_GRID_STORE_ID": true, - "SALES_ORDER_GRID_BASE_GRAND_TOTAL": true, - "SALES_ORDER_GRID_BASE_TOTAL_PAID": true, - "SALES_ORDER_GRID_GRAND_TOTAL": true, - "SALES_ORDER_GRID_TOTAL_PAID": true, - "SALES_ORDER_GRID_SHIPPING_NAME": true, - "SALES_ORDER_GRID_BILLING_NAME": true, - "SALES_ORDER_GRID_CREATED_AT": true, - "SALES_ORDER_GRID_CUSTOMER_ID": true, - "SALES_ORDER_GRID_UPDATED_AT": true, - "FTI_65B9E9925EC58F0C7C2E2F6379C233E7": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_GRID_INCREMENT_ID_STORE_ID": true - } - }, - "sales_order_address": { - "column": { - "entity_id": true, - "parent_id": true, - "customer_address_id": true, - "quote_address_id": true, - "region_id": true, - "customer_id": true, - "fax": true, - "region": true, - "postcode": true, - "lastname": true, - "street": true, - "city": true, - "email": true, - "telephone": true, - "country_id": true, - "firstname": true, - "address_type": true, - "prefix": true, - "middlename": true, - "suffix": true, - "company": true, - "vat_id": true, - "vat_is_valid": true, - "vat_request_id": true, - "vat_request_date": true, - "vat_request_success": true - }, - "index": { - "SALES_ORDER_ADDRESS_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_ADDRESS_PARENT_ID_SALES_ORDER_ENTITY_ID": true - } - }, - "sales_order_status_history": { - "column": { - "entity_id": true, - "parent_id": true, - "is_customer_notified": true, - "is_visible_on_front": true, - "comment": true, - "status": true, - "created_at": true, - "entity_name": true - }, - "index": { - "SALES_ORDER_STATUS_HISTORY_PARENT_ID": true, - "SALES_ORDER_STATUS_HISTORY_CREATED_AT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_STATUS_HISTORY_PARENT_ID_SALES_ORDER_ENTITY_ID": true - } - }, - "sales_order_item": { - "column": { - "item_id": true, - "order_id": true, - "parent_item_id": true, - "quote_item_id": true, - "store_id": true, - "created_at": true, - "updated_at": true, - "product_id": true, - "product_type": true, - "product_options": true, - "weight": true, - "is_virtual": true, - "sku": true, - "name": true, - "description": true, - "applied_rule_ids": true, - "additional_data": true, - "is_qty_decimal": true, - "no_discount": true, - "qty_backordered": true, - "qty_canceled": true, - "qty_invoiced": true, - "qty_ordered": true, - "qty_refunded": true, - "qty_shipped": true, - "base_cost": true, - "price": true, - "base_price": true, - "original_price": true, - "base_original_price": true, - "tax_percent": true, - "tax_amount": true, - "base_tax_amount": true, - "tax_invoiced": true, - "base_tax_invoiced": true, - "discount_percent": true, - "discount_amount": true, - "base_discount_amount": true, - "discount_invoiced": true, - "base_discount_invoiced": true, - "amount_refunded": true, - "base_amount_refunded": true, - "row_total": true, - "base_row_total": true, - "row_invoiced": true, - "base_row_invoiced": true, - "row_weight": true, - "base_tax_before_discount": true, - "tax_before_discount": true, - "ext_order_item_id": true, - "locked_do_invoice": true, - "locked_do_ship": true, - "price_incl_tax": true, - "base_price_incl_tax": true, - "row_total_incl_tax": true, - "base_row_total_incl_tax": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "discount_tax_compensation_invoiced": true, - "base_discount_tax_compensation_invoiced": true, - "discount_tax_compensation_refunded": true, - "base_discount_tax_compensation_refunded": true, - "tax_canceled": true, - "discount_tax_compensation_canceled": true, - "tax_refunded": true, - "base_tax_refunded": true, - "discount_refunded": true, - "base_discount_refunded": true - }, - "index": { - "SALES_ORDER_ITEM_ORDER_ID": true, - "SALES_ORDER_ITEM_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_ITEM_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "SALES_ORDER_ITEM_STORE_ID_STORE_STORE_ID": true - } - }, - "sales_order_payment": { - "column": { - "entity_id": true, - "parent_id": true, - "base_shipping_captured": true, - "shipping_captured": true, - "amount_refunded": true, - "base_amount_paid": true, - "amount_canceled": true, - "base_amount_authorized": true, - "base_amount_paid_online": true, - "base_amount_refunded_online": true, - "base_shipping_amount": true, - "shipping_amount": true, - "amount_paid": true, - "amount_authorized": true, - "base_amount_ordered": true, - "base_shipping_refunded": true, - "shipping_refunded": true, - "base_amount_refunded": true, - "amount_ordered": true, - "base_amount_canceled": true, - "quote_payment_id": true, - "additional_data": true, - "cc_exp_month": true, - "cc_ss_start_year": true, - "echeck_bank_name": true, - "method": true, - "cc_debug_request_body": true, - "cc_secure_verify": true, - "protection_eligibility": true, - "cc_approval": true, - "cc_last_4": true, - "cc_status_description": true, - "echeck_type": true, - "cc_debug_response_serialized": true, - "cc_ss_start_month": true, - "echeck_account_type": true, - "last_trans_id": true, - "cc_cid_status": true, - "cc_owner": true, - "cc_type": true, - "po_number": true, - "cc_exp_year": true, - "cc_status": true, - "echeck_routing_number": true, - "account_status": true, - "anet_trans_method": true, - "cc_debug_response_body": true, - "cc_ss_issue": true, - "echeck_account_name": true, - "cc_avs_status": true, - "cc_number_enc": true, - "cc_trans_id": true, - "address_status": true, - "additional_information": true - }, - "index": { - "SALES_ORDER_PAYMENT_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_PAYMENT_PARENT_ID_SALES_ORDER_ENTITY_ID": true - } - }, - "sales_shipment": { - "column": { - "entity_id": true, - "store_id": true, - "total_weight": true, - "total_qty": true, - "email_sent": true, - "send_email": true, - "order_id": true, - "customer_id": true, - "shipping_address_id": true, - "billing_address_id": true, - "shipment_status": true, - "increment_id": true, - "created_at": true, - "updated_at": true, - "packages": true, - "shipping_label": true, - "customer_note": true, - "customer_note_notify": true - }, - "index": { - "SALES_SHIPMENT_STORE_ID": true, - "SALES_SHIPMENT_TOTAL_QTY": true, - "SALES_SHIPMENT_ORDER_ID": true, - "SALES_SHIPMENT_CREATED_AT": true, - "SALES_SHIPMENT_UPDATED_AT": true, - "SALES_SHIPMENT_SEND_EMAIL": true, - "SALES_SHIPMENT_EMAIL_SENT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPMENT_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "SALES_SHIPMENT_STORE_ID_STORE_STORE_ID": true, - "SALES_SHIPMENT_INCREMENT_ID_STORE_ID": true - } - }, - "sales_shipment_grid": { - "column": { - "entity_id": true, - "increment_id": true, - "store_id": true, - "order_increment_id": true, - "order_id": true, - "order_created_at": true, - "customer_name": true, - "total_qty": true, - "shipment_status": true, - "order_status": true, - "billing_address": true, - "shipping_address": true, - "billing_name": true, - "shipping_name": true, - "customer_email": true, - "customer_group_id": true, - "payment_method": true, - "shipping_information": true, - "created_at": true, - "updated_at": true - }, - "index": { - "SALES_SHIPMENT_GRID_STORE_ID": true, - "SALES_SHIPMENT_GRID_TOTAL_QTY": true, - "SALES_SHIPMENT_GRID_ORDER_INCREMENT_ID": true, - "SALES_SHIPMENT_GRID_SHIPMENT_STATUS": true, - "SALES_SHIPMENT_GRID_ORDER_STATUS": true, - "SALES_SHIPMENT_GRID_CREATED_AT": true, - "SALES_SHIPMENT_GRID_UPDATED_AT": true, - "SALES_SHIPMENT_GRID_ORDER_CREATED_AT": true, - "SALES_SHIPMENT_GRID_SHIPPING_NAME": true, - "SALES_SHIPMENT_GRID_BILLING_NAME": true, - "FTI_086B40C8955F167B8EA76653437879B4": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPMENT_GRID_INCREMENT_ID_STORE_ID": true - } - }, - "sales_shipment_item": { - "column": { - "entity_id": true, - "parent_id": true, - "row_total": true, - "price": true, - "weight": true, - "qty": true, - "product_id": true, - "order_item_id": true, - "additional_data": true, - "description": true, - "name": true, - "sku": true - }, - "index": { - "SALES_SHIPMENT_ITEM_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPMENT_ITEM_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true - } - }, - "sales_shipment_track": { - "column": { - "entity_id": true, - "parent_id": true, - "weight": true, - "qty": true, - "order_id": true, - "track_number": true, - "description": true, - "title": true, - "carrier_code": true, - "created_at": true, - "updated_at": true - }, - "index": { - "SALES_SHIPMENT_TRACK_PARENT_ID": true, - "SALES_SHIPMENT_TRACK_ORDER_ID": true, - "SALES_SHIPMENT_TRACK_CREATED_AT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPMENT_TRACK_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true - } - }, - "sales_shipment_comment": { - "column": { - "entity_id": true, - "parent_id": true, - "is_customer_notified": true, - "is_visible_on_front": true, - "comment": true, - "created_at": true - }, - "index": { - "SALES_SHIPMENT_COMMENT_CREATED_AT": true, - "SALES_SHIPMENT_COMMENT_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPMENT_COMMENT_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true - } - }, - "sales_invoice": { - "column": { - "entity_id": true, - "store_id": true, - "base_grand_total": true, - "shipping_tax_amount": true, - "tax_amount": true, - "base_tax_amount": true, - "store_to_order_rate": true, - "base_shipping_tax_amount": true, - "base_discount_amount": true, - "base_to_order_rate": true, - "grand_total": true, - "shipping_amount": true, - "subtotal_incl_tax": true, - "base_subtotal_incl_tax": true, - "store_to_base_rate": true, - "base_shipping_amount": true, - "total_qty": true, - "base_to_global_rate": true, - "subtotal": true, - "base_subtotal": true, - "discount_amount": true, - "billing_address_id": true, - "is_used_for_refund": true, - "order_id": true, - "email_sent": true, - "send_email": true, - "can_void_flag": true, - "state": true, - "shipping_address_id": true, - "store_currency_code": true, - "transaction_id": true, - "order_currency_code": true, - "base_currency_code": true, - "global_currency_code": true, - "increment_id": true, - "created_at": true, - "updated_at": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "shipping_discount_tax_compensation_amount": true, - "base_shipping_discount_tax_compensation_amnt": true, - "shipping_incl_tax": true, - "base_shipping_incl_tax": true, - "base_total_refunded": true, - "discount_description": true, - "customer_note": true, - "customer_note_notify": true - }, - "index": { - "SALES_INVOICE_STORE_ID": true, - "SALES_INVOICE_GRAND_TOTAL": true, - "SALES_INVOICE_ORDER_ID": true, - "SALES_INVOICE_STATE": true, - "SALES_INVOICE_CREATED_AT": true, - "SALES_INVOICE_UPDATED_AT": true, - "SALES_INVOICE_SEND_EMAIL": true, - "SALES_INVOICE_EMAIL_SENT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICE_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "SALES_INVOICE_STORE_ID_STORE_STORE_ID": true, - "SALES_INVOICE_INCREMENT_ID_STORE_ID": true - } - }, - "sales_invoice_grid": { - "column": { - "entity_id": true, - "increment_id": true, - "state": true, - "store_id": true, - "store_name": true, - "order_id": true, - "order_increment_id": true, - "order_created_at": true, - "customer_name": true, - "customer_email": true, - "customer_group_id": true, - "payment_method": true, - "store_currency_code": true, - "order_currency_code": true, - "base_currency_code": true, - "global_currency_code": true, - "billing_name": true, - "billing_address": true, - "shipping_address": true, - "shipping_information": true, - "subtotal": true, - "shipping_and_handling": true, - "grand_total": true, - "created_at": true, - "updated_at": true, - "base_grand_total": true - }, - "index": { - "SALES_INVOICE_GRID_STORE_ID": true, - "SALES_INVOICE_GRID_GRAND_TOTAL": true, - "SALES_INVOICE_GRID_ORDER_ID": true, - "SALES_INVOICE_GRID_STATE": true, - "SALES_INVOICE_GRID_ORDER_INCREMENT_ID": true, - "SALES_INVOICE_GRID_CREATED_AT": true, - "SALES_INVOICE_GRID_UPDATED_AT": true, - "SALES_INVOICE_GRID_ORDER_CREATED_AT": true, - "SALES_INVOICE_GRID_BILLING_NAME": true, - "FTI_95D9C924DD6A8734EB8B5D01D60F90DE": true, - "SALES_INVOICE_GRID_BASE_GRAND_TOTAL": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICE_GRID_INCREMENT_ID_STORE_ID": true - } - }, - "sales_invoice_item": { - "column": { - "entity_id": true, - "parent_id": true, - "base_price": true, - "tax_amount": true, - "base_row_total": true, - "discount_amount": true, - "row_total": true, - "base_discount_amount": true, - "price_incl_tax": true, - "base_tax_amount": true, - "base_price_incl_tax": true, - "qty": true, - "base_cost": true, - "price": true, - "base_row_total_incl_tax": true, - "row_total_incl_tax": true, - "product_id": true, - "order_item_id": true, - "additional_data": true, - "description": true, - "sku": true, - "name": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "tax_ratio": true - }, - "index": { - "SALES_INVOICE_ITEM_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICE_ITEM_PARENT_ID_SALES_INVOICE_ENTITY_ID": true - } - }, - "sales_invoice_comment": { - "column": { - "entity_id": true, - "parent_id": true, - "is_customer_notified": true, - "is_visible_on_front": true, - "comment": true, - "created_at": true - }, - "index": { - "SALES_INVOICE_COMMENT_CREATED_AT": true, - "SALES_INVOICE_COMMENT_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICE_COMMENT_PARENT_ID_SALES_INVOICE_ENTITY_ID": true - } - }, - "sales_creditmemo": { - "column": { - "entity_id": true, - "store_id": true, - "adjustment_positive": true, - "base_shipping_tax_amount": true, - "store_to_order_rate": true, - "base_discount_amount": true, - "base_to_order_rate": true, - "grand_total": true, - "base_adjustment_negative": true, - "base_subtotal_incl_tax": true, - "shipping_amount": true, - "subtotal_incl_tax": true, - "adjustment_negative": true, - "base_shipping_amount": true, - "store_to_base_rate": true, - "base_to_global_rate": true, - "base_adjustment": true, - "base_subtotal": true, - "discount_amount": true, - "subtotal": true, - "adjustment": true, - "base_grand_total": true, - "base_adjustment_positive": true, - "base_tax_amount": true, - "shipping_tax_amount": true, - "tax_amount": true, - "order_id": true, - "email_sent": true, - "send_email": true, - "creditmemo_status": true, - "state": true, - "shipping_address_id": true, - "billing_address_id": true, - "invoice_id": true, - "store_currency_code": true, - "order_currency_code": true, - "base_currency_code": true, - "global_currency_code": true, - "transaction_id": true, - "increment_id": true, - "created_at": true, - "updated_at": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "shipping_discount_tax_compensation_amount": true, - "base_shipping_discount_tax_compensation_amnt": true, - "shipping_incl_tax": true, - "base_shipping_incl_tax": true, - "discount_description": true, - "customer_note": true, - "customer_note_notify": true - }, - "index": { - "SALES_CREDITMEMO_STORE_ID": true, - "SALES_CREDITMEMO_ORDER_ID": true, - "SALES_CREDITMEMO_CREDITMEMO_STATUS": true, - "SALES_CREDITMEMO_STATE": true, - "SALES_CREDITMEMO_CREATED_AT": true, - "SALES_CREDITMEMO_UPDATED_AT": true, - "SALES_CREDITMEMO_SEND_EMAIL": true, - "SALES_CREDITMEMO_EMAIL_SENT": true - }, - "constraint": { - "PRIMARY": true, - "SALES_CREDITMEMO_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "SALES_CREDITMEMO_STORE_ID_STORE_STORE_ID": true, - "SALES_CREDITMEMO_INCREMENT_ID_STORE_ID": true - } - }, - "sales_creditmemo_grid": { - "column": { - "entity_id": true, - "increment_id": true, - "created_at": true, - "updated_at": true, - "order_id": true, - "order_increment_id": true, - "order_created_at": true, - "billing_name": true, - "state": true, - "base_grand_total": true, - "order_status": true, - "store_id": true, - "billing_address": true, - "shipping_address": true, - "customer_name": true, - "customer_email": true, - "customer_group_id": true, - "payment_method": true, - "shipping_information": true, - "subtotal": true, - "shipping_and_handling": true, - "adjustment_positive": true, - "adjustment_negative": true, - "order_base_grand_total": true - }, - "index": { - "SALES_CREDITMEMO_GRID_ORDER_INCREMENT_ID": true, - "SALES_CREDITMEMO_GRID_CREATED_AT": true, - "SALES_CREDITMEMO_GRID_UPDATED_AT": true, - "SALES_CREDITMEMO_GRID_ORDER_CREATED_AT": true, - "SALES_CREDITMEMO_GRID_STATE": true, - "SALES_CREDITMEMO_GRID_BILLING_NAME": true, - "SALES_CREDITMEMO_GRID_ORDER_STATUS": true, - "SALES_CREDITMEMO_GRID_BASE_GRAND_TOTAL": true, - "SALES_CREDITMEMO_GRID_STORE_ID": true, - "SALES_CREDITMEMO_GRID_ORDER_BASE_GRAND_TOTAL": true, - "SALES_CREDITMEMO_GRID_ORDER_ID": true, - "FTI_32B7BA885941A8254EE84AE650ABDC86": true - }, - "constraint": { - "PRIMARY": true, - "SALES_CREDITMEMO_GRID_INCREMENT_ID_STORE_ID": true - } - }, - "sales_creditmemo_item": { - "column": { - "entity_id": true, - "parent_id": true, - "base_price": true, - "tax_amount": true, - "base_row_total": true, - "discount_amount": true, - "row_total": true, - "base_discount_amount": true, - "price_incl_tax": true, - "base_tax_amount": true, - "base_price_incl_tax": true, - "qty": true, - "base_cost": true, - "price": true, - "base_row_total_incl_tax": true, - "row_total_incl_tax": true, - "product_id": true, - "order_item_id": true, - "additional_data": true, - "description": true, - "sku": true, - "name": true, - "discount_tax_compensation_amount": true, - "base_discount_tax_compensation_amount": true, - "tax_ratio": true - }, - "index": { - "SALES_CREDITMEMO_ITEM_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_CREDITMEMO_ITEM_PARENT_ID_SALES_CREDITMEMO_ENTITY_ID": true - } - }, - "sales_creditmemo_comment": { - "column": { - "entity_id": true, - "parent_id": true, - "is_customer_notified": true, - "is_visible_on_front": true, - "comment": true, - "created_at": true - }, - "index": { - "SALES_CREDITMEMO_COMMENT_CREATED_AT": true, - "SALES_CREDITMEMO_COMMENT_PARENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_CREDITMEMO_COMMENT_PARENT_ID_SALES_CREDITMEMO_ENTITY_ID": true - } - }, - "sales_invoiced_aggregated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "orders_invoiced": true, - "invoiced": true, - "invoiced_captured": true, - "invoiced_not_captured": true - }, - "index": { - "SALES_INVOICED_AGGREGATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICED_AGGREGATED_STORE_ID_STORE_STORE_ID": true, - "SALES_INVOICED_AGGREGATED_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_invoiced_aggregated_order": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "orders_invoiced": true, - "invoiced": true, - "invoiced_captured": true, - "invoiced_not_captured": true - }, - "index": { - "SALES_INVOICED_AGGREGATED_ORDER_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_INVOICED_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, - "SALES_INVOICED_AGGREGATED_ORDER_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_order_aggregated_created": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "total_qty_ordered": true, - "total_qty_invoiced": true, - "total_income_amount": true, - "total_revenue_amount": true, - "total_profit_amount": true, - "total_invoiced_amount": true, - "total_canceled_amount": true, - "total_paid_amount": true, - "total_refunded_amount": true, - "total_tax_amount": true, - "total_tax_amount_actual": true, - "total_shipping_amount": true, - "total_shipping_amount_actual": true, - "total_discount_amount": true, - "total_discount_amount_actual": true - }, - "index": { - "SALES_ORDER_AGGREGATED_CREATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_AGGREGATED_CREATED_STORE_ID_STORE_STORE_ID": true, - "SALES_ORDER_AGGREGATED_CREATED_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_order_aggregated_updated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "total_qty_ordered": true, - "total_qty_invoiced": true, - "total_income_amount": true, - "total_revenue_amount": true, - "total_profit_amount": true, - "total_invoiced_amount": true, - "total_canceled_amount": true, - "total_paid_amount": true, - "total_refunded_amount": true, - "total_tax_amount": true, - "total_tax_amount_actual": true, - "total_shipping_amount": true, - "total_shipping_amount_actual": true, - "total_discount_amount": true, - "total_discount_amount_actual": true - }, - "index": { - "SALES_ORDER_AGGREGATED_UPDATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, - "SALES_ORDER_AGGREGATED_UPDATED_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_payment_transaction": { - "column": { - "transaction_id": true, - "parent_id": true, - "order_id": true, - "payment_id": true, - "txn_id": true, - "parent_txn_id": true, - "txn_type": true, - "is_closed": true, - "additional_information": true, - "created_at": true - }, - "index": { - "SALES_PAYMENT_TRANSACTION_PARENT_ID": true, - "SALES_PAYMENT_TRANSACTION_PAYMENT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_PAYMENT_TRANSACTION_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "FK_B99FF1A06402D725EBDB0F3A7ECD47A2": true, - "SALES_PAYMENT_TRANSACTION_PAYMENT_ID_SALES_ORDER_PAYMENT_ENTT_ID": true, - "SALES_PAYMENT_TRANSACTION_ORDER_ID_PAYMENT_ID_TXN_ID": true - } - }, - "sales_refunded_aggregated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "refunded": true, - "online_refunded": true, - "offline_refunded": true - }, - "index": { - "SALES_REFUNDED_AGGREGATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_REFUNDED_AGGREGATED_STORE_ID_STORE_STORE_ID": true, - "SALES_REFUNDED_AGGREGATED_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_refunded_aggregated_order": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "orders_count": true, - "refunded": true, - "online_refunded": true, - "offline_refunded": true - }, - "index": { - "SALES_REFUNDED_AGGREGATED_ORDER_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_REFUNDED_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, - "SALES_REFUNDED_AGGREGATED_ORDER_PERIOD_STORE_ID_ORDER_STATUS": true - } - }, - "sales_shipping_aggregated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "shipping_description": true, - "orders_count": true, - "total_shipping": true, - "total_shipping_actual": true - }, - "index": { - "SALES_SHIPPING_AGGREGATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPPING_AGGREGATED_STORE_ID_STORE_STORE_ID": true, - "SALES_SHPP_AGGRED_PERIOD_STORE_ID_ORDER_STS_SHPP_DESCRIPTION": true - } - }, - "sales_shipping_aggregated_order": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "shipping_description": true, - "orders_count": true, - "total_shipping": true, - "total_shipping_actual": true - }, - "index": { - "SALES_SHIPPING_AGGREGATED_ORDER_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SHIPPING_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, - "UNQ_C05FAE47282EEA68654D0924E946761F": true - } - }, - "sales_bestsellers_aggregated_daily": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "qty_ordered": true, - "rating_pos": true - }, - "index": { - "SALES_BESTSELLERS_AGGREGATED_DAILY_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_DAILY_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_BESTSELLERS_AGGREGATED_DAILY_STORE_ID_STORE_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_DAILY_PERIOD_STORE_ID_PRODUCT_ID": true - } - }, - "sales_bestsellers_aggregated_monthly": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "qty_ordered": true, - "rating_pos": true - }, - "index": { - "SALES_BESTSELLERS_AGGREGATED_MONTHLY_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_MONTHLY_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_BESTSELLERS_AGGREGATED_MONTHLY_STORE_ID_STORE_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_MONTHLY_PERIOD_STORE_ID_PRODUCT_ID": true - } - }, - "sales_bestsellers_aggregated_yearly": { - "column": { - "id": true, - "period": true, - "store_id": true, - "product_id": true, - "product_name": true, - "product_price": true, - "qty_ordered": true, - "rating_pos": true - }, - "index": { - "SALES_BESTSELLERS_AGGREGATED_YEARLY_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_YEARLY_PRODUCT_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_BESTSELLERS_AGGREGATED_YEARLY_STORE_ID_STORE_STORE_ID": true, - "SALES_BESTSELLERS_AGGREGATED_YEARLY_PERIOD_STORE_ID_PRODUCT_ID": true - } - }, - "sales_order_tax": { - "column": { - "tax_id": true, - "order_id": true, - "code": true, - "title": true, - "percent": true, - "amount": true, - "priority": true, - "position": true, - "base_amount": true, - "process": true, - "base_real_amount": true - }, - "index": { - "SALES_ORDER_TAX_ORDER_ID_PRIORITY_POSITION": true - }, - "constraint": { - "PRIMARY": true - } - }, - "sales_order_tax_item": { - "column": { - "tax_item_id": true, - "tax_id": true, - "item_id": true, - "tax_percent": true, - "amount": true, - "base_amount": true, - "real_amount": true, - "real_base_amount": true, - "associated_item_id": true, - "taxable_item_type": true - }, - "index": { - "SALES_ORDER_TAX_ITEM_ITEM_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_TAX_ITEM_ASSOCIATED_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true, - "SALES_ORDER_TAX_ITEM_TAX_ID_SALES_ORDER_TAX_TAX_ID": true, - "SALES_ORDER_TAX_ITEM_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true, - "SALES_ORDER_TAX_ITEM_TAX_ID_ITEM_ID": true - } - }, - "sales_order_status": { - "column": { - "status": true, - "label": true - }, - "constraint": { - "PRIMARY": true - } - }, - "sales_order_status_state": { - "column": { - "status": true, - "state": true, - "is_default": true, - "visible_on_front": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_STATUS_STATE_STATUS_SALES_ORDER_STATUS_STATUS": true - } - }, - "sales_order_status_label": { - "column": { - "status": true, - "store_id": true, - "label": true - }, - "index": { - "SALES_ORDER_STATUS_LABEL_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALES_ORDER_STATUS_LABEL_STATUS_SALES_ORDER_STATUS_STATUS": true, - "SALES_ORDER_STATUS_LABEL_STORE_ID_STORE_STORE_ID": true + "sales_order": { + "column": { + "entity_id": true, + "state": true, + "status": true, + "coupon_code": true, + "protect_code": true, + "shipping_description": true, + "is_virtual": true, + "store_id": true, + "customer_id": true, + "base_discount_amount": true, + "base_discount_canceled": true, + "base_discount_invoiced": true, + "base_discount_refunded": true, + "base_grand_total": true, + "base_shipping_amount": true, + "base_shipping_canceled": true, + "base_shipping_invoiced": true, + "base_shipping_refunded": true, + "base_shipping_tax_amount": true, + "base_shipping_tax_refunded": true, + "base_subtotal": true, + "base_subtotal_canceled": true, + "base_subtotal_invoiced": true, + "base_subtotal_refunded": true, + "base_tax_amount": true, + "base_tax_canceled": true, + "base_tax_invoiced": true, + "base_tax_refunded": true, + "base_to_global_rate": true, + "base_to_order_rate": true, + "base_total_canceled": true, + "base_total_invoiced": true, + "base_total_invoiced_cost": true, + "base_total_offline_refunded": true, + "base_total_online_refunded": true, + "base_total_paid": true, + "base_total_qty_ordered": true, + "base_total_refunded": true, + "discount_amount": true, + "discount_canceled": true, + "discount_invoiced": true, + "discount_refunded": true, + "grand_total": true, + "shipping_amount": true, + "shipping_canceled": true, + "shipping_invoiced": true, + "shipping_refunded": true, + "shipping_tax_amount": true, + "shipping_tax_refunded": true, + "store_to_base_rate": true, + "store_to_order_rate": true, + "subtotal": true, + "subtotal_canceled": true, + "subtotal_invoiced": true, + "subtotal_refunded": true, + "tax_amount": true, + "tax_canceled": true, + "tax_invoiced": true, + "tax_refunded": true, + "total_canceled": true, + "total_invoiced": true, + "total_offline_refunded": true, + "total_online_refunded": true, + "total_paid": true, + "total_qty_ordered": true, + "total_refunded": true, + "can_ship_partially": true, + "can_ship_partially_item": true, + "customer_is_guest": true, + "customer_note_notify": true, + "billing_address_id": true, + "customer_group_id": true, + "edit_increment": true, + "email_sent": true, + "send_email": true, + "forced_shipment_with_invoice": true, + "payment_auth_expiration": true, + "quote_address_id": true, + "quote_id": true, + "shipping_address_id": true, + "adjustment_negative": true, + "adjustment_positive": true, + "base_adjustment_negative": true, + "base_adjustment_positive": true, + "base_shipping_discount_amount": true, + "base_subtotal_incl_tax": true, + "base_total_due": true, + "payment_authorization_amount": true, + "shipping_discount_amount": true, + "subtotal_incl_tax": true, + "total_due": true, + "weight": true, + "customer_dob": true, + "increment_id": true, + "applied_rule_ids": true, + "base_currency_code": true, + "customer_email": true, + "customer_firstname": true, + "customer_lastname": true, + "customer_middlename": true, + "customer_prefix": true, + "customer_suffix": true, + "customer_taxvat": true, + "discount_description": true, + "ext_customer_id": true, + "ext_order_id": true, + "global_currency_code": true, + "hold_before_state": true, + "hold_before_status": true, + "order_currency_code": true, + "original_increment_id": true, + "relation_child_id": true, + "relation_child_real_id": true, + "relation_parent_id": true, + "relation_parent_real_id": true, + "remote_ip": true, + "shipping_method": true, + "store_currency_code": true, + "store_name": true, + "x_forwarded_for": true, + "customer_note": true, + "created_at": true, + "updated_at": true, + "total_item_count": true, + "customer_gender": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "shipping_discount_tax_compensation_amount": true, + "base_shipping_discount_tax_compensation_amnt": true, + "discount_tax_compensation_invoiced": true, + "base_discount_tax_compensation_invoiced": true, + "discount_tax_compensation_refunded": true, + "base_discount_tax_compensation_refunded": true, + "shipping_incl_tax": true, + "base_shipping_incl_tax": true, + "coupon_rule_name": true + }, + "index": { + "SALES_ORDER_STATUS": true, + "SALES_ORDER_STATE": true, + "SALES_ORDER_STORE_ID": true, + "SALES_ORDER_CREATED_AT": true, + "SALES_ORDER_CUSTOMER_ID": true, + "SALES_ORDER_EXT_ORDER_ID": true, + "SALES_ORDER_QUOTE_ID": true, + "SALES_ORDER_UPDATED_AT": true, + "SALES_ORDER_SEND_EMAIL": true, + "SALES_ORDER_EMAIL_SENT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "SALES_ORDER_STORE_ID_STORE_STORE_ID": true, + "SALES_ORDER_INCREMENT_ID_STORE_ID": true + } + }, + "sales_order_grid": { + "column": { + "entity_id": true, + "status": true, + "store_id": true, + "store_name": true, + "customer_id": true, + "base_grand_total": true, + "base_total_paid": true, + "grand_total": true, + "total_paid": true, + "increment_id": true, + "base_currency_code": true, + "order_currency_code": true, + "shipping_name": true, + "billing_name": true, + "created_at": true, + "updated_at": true, + "billing_address": true, + "shipping_address": true, + "shipping_information": true, + "customer_email": true, + "customer_group": true, + "subtotal": true, + "shipping_and_handling": true, + "customer_name": true, + "payment_method": true, + "total_refunded": true + }, + "index": { + "SALES_ORDER_GRID_STATUS": true, + "SALES_ORDER_GRID_STORE_ID": true, + "SALES_ORDER_GRID_BASE_GRAND_TOTAL": true, + "SALES_ORDER_GRID_BASE_TOTAL_PAID": true, + "SALES_ORDER_GRID_GRAND_TOTAL": true, + "SALES_ORDER_GRID_TOTAL_PAID": true, + "SALES_ORDER_GRID_SHIPPING_NAME": true, + "SALES_ORDER_GRID_BILLING_NAME": true, + "SALES_ORDER_GRID_CREATED_AT": true, + "SALES_ORDER_GRID_CUSTOMER_ID": true, + "SALES_ORDER_GRID_UPDATED_AT": true, + "FTI_65B9E9925EC58F0C7C2E2F6379C233E7": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_GRID_INCREMENT_ID_STORE_ID": true + } + }, + "sales_order_address": { + "column": { + "entity_id": true, + "parent_id": true, + "customer_address_id": true, + "quote_address_id": true, + "region_id": true, + "customer_id": true, + "fax": true, + "region": true, + "postcode": true, + "lastname": true, + "street": true, + "city": true, + "email": true, + "telephone": true, + "country_id": true, + "firstname": true, + "address_type": true, + "prefix": true, + "middlename": true, + "suffix": true, + "company": true, + "vat_id": true, + "vat_is_valid": true, + "vat_request_id": true, + "vat_request_date": true, + "vat_request_success": true + }, + "index": { + "SALES_ORDER_ADDRESS_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_ADDRESS_PARENT_ID_SALES_ORDER_ENTITY_ID": true + } + }, + "sales_order_status_history": { + "column": { + "entity_id": true, + "parent_id": true, + "is_customer_notified": true, + "is_visible_on_front": true, + "comment": true, + "status": true, + "created_at": true, + "entity_name": true + }, + "index": { + "SALES_ORDER_STATUS_HISTORY_PARENT_ID": true, + "SALES_ORDER_STATUS_HISTORY_CREATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_STATUS_HISTORY_PARENT_ID_SALES_ORDER_ENTITY_ID": true + } + }, + "sales_order_item": { + "column": { + "item_id": true, + "order_id": true, + "parent_item_id": true, + "quote_item_id": true, + "store_id": true, + "created_at": true, + "updated_at": true, + "product_id": true, + "product_type": true, + "product_options": true, + "weight": true, + "is_virtual": true, + "sku": true, + "name": true, + "description": true, + "applied_rule_ids": true, + "additional_data": true, + "is_qty_decimal": true, + "no_discount": true, + "qty_backordered": true, + "qty_canceled": true, + "qty_invoiced": true, + "qty_ordered": true, + "qty_refunded": true, + "qty_shipped": true, + "base_cost": true, + "price": true, + "base_price": true, + "original_price": true, + "base_original_price": true, + "tax_percent": true, + "tax_amount": true, + "base_tax_amount": true, + "tax_invoiced": true, + "base_tax_invoiced": true, + "discount_percent": true, + "discount_amount": true, + "base_discount_amount": true, + "discount_invoiced": true, + "base_discount_invoiced": true, + "amount_refunded": true, + "base_amount_refunded": true, + "row_total": true, + "base_row_total": true, + "row_invoiced": true, + "base_row_invoiced": true, + "row_weight": true, + "base_tax_before_discount": true, + "tax_before_discount": true, + "ext_order_item_id": true, + "locked_do_invoice": true, + "locked_do_ship": true, + "price_incl_tax": true, + "base_price_incl_tax": true, + "row_total_incl_tax": true, + "base_row_total_incl_tax": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "discount_tax_compensation_invoiced": true, + "base_discount_tax_compensation_invoiced": true, + "discount_tax_compensation_refunded": true, + "base_discount_tax_compensation_refunded": true, + "tax_canceled": true, + "discount_tax_compensation_canceled": true, + "tax_refunded": true, + "base_tax_refunded": true, + "discount_refunded": true, + "base_discount_refunded": true + }, + "index": { + "SALES_ORDER_ITEM_ORDER_ID": true, + "SALES_ORDER_ITEM_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_ITEM_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "SALES_ORDER_ITEM_STORE_ID_STORE_STORE_ID": true + } + }, + "sales_order_payment": { + "column": { + "entity_id": true, + "parent_id": true, + "base_shipping_captured": true, + "shipping_captured": true, + "amount_refunded": true, + "base_amount_paid": true, + "amount_canceled": true, + "base_amount_authorized": true, + "base_amount_paid_online": true, + "base_amount_refunded_online": true, + "base_shipping_amount": true, + "shipping_amount": true, + "amount_paid": true, + "amount_authorized": true, + "base_amount_ordered": true, + "base_shipping_refunded": true, + "shipping_refunded": true, + "base_amount_refunded": true, + "amount_ordered": true, + "base_amount_canceled": true, + "quote_payment_id": true, + "additional_data": true, + "cc_exp_month": true, + "cc_ss_start_year": true, + "echeck_bank_name": true, + "method": true, + "cc_debug_request_body": true, + "cc_secure_verify": true, + "protection_eligibility": true, + "cc_approval": true, + "cc_last_4": true, + "cc_status_description": true, + "echeck_type": true, + "cc_debug_response_serialized": true, + "cc_ss_start_month": true, + "echeck_account_type": true, + "last_trans_id": true, + "cc_cid_status": true, + "cc_owner": true, + "cc_type": true, + "po_number": true, + "cc_exp_year": true, + "cc_status": true, + "echeck_routing_number": true, + "account_status": true, + "anet_trans_method": true, + "cc_debug_response_body": true, + "cc_ss_issue": true, + "echeck_account_name": true, + "cc_avs_status": true, + "cc_number_enc": true, + "cc_trans_id": true, + "address_status": true, + "additional_information": true + }, + "index": { + "SALES_ORDER_PAYMENT_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_PAYMENT_PARENT_ID_SALES_ORDER_ENTITY_ID": true + } + }, + "sales_shipment": { + "column": { + "entity_id": true, + "store_id": true, + "total_weight": true, + "total_qty": true, + "email_sent": true, + "send_email": true, + "order_id": true, + "customer_id": true, + "shipping_address_id": true, + "billing_address_id": true, + "shipment_status": true, + "increment_id": true, + "created_at": true, + "updated_at": true, + "packages": true, + "shipping_label": true, + "customer_note": true, + "customer_note_notify": true + }, + "index": { + "SALES_SHIPMENT_STORE_ID": true, + "SALES_SHIPMENT_TOTAL_QTY": true, + "SALES_SHIPMENT_ORDER_ID": true, + "SALES_SHIPMENT_CREATED_AT": true, + "SALES_SHIPMENT_UPDATED_AT": true, + "SALES_SHIPMENT_SEND_EMAIL": true, + "SALES_SHIPMENT_EMAIL_SENT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPMENT_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "SALES_SHIPMENT_STORE_ID_STORE_STORE_ID": true, + "SALES_SHIPMENT_INCREMENT_ID_STORE_ID": true + } + }, + "sales_shipment_grid": { + "column": { + "entity_id": true, + "increment_id": true, + "store_id": true, + "order_increment_id": true, + "order_id": true, + "order_created_at": true, + "customer_name": true, + "total_qty": true, + "shipment_status": true, + "order_status": true, + "billing_address": true, + "shipping_address": true, + "billing_name": true, + "shipping_name": true, + "customer_email": true, + "customer_group_id": true, + "payment_method": true, + "shipping_information": true, + "created_at": true, + "updated_at": true + }, + "index": { + "SALES_SHIPMENT_GRID_STORE_ID": true, + "SALES_SHIPMENT_GRID_TOTAL_QTY": true, + "SALES_SHIPMENT_GRID_ORDER_INCREMENT_ID": true, + "SALES_SHIPMENT_GRID_SHIPMENT_STATUS": true, + "SALES_SHIPMENT_GRID_ORDER_STATUS": true, + "SALES_SHIPMENT_GRID_CREATED_AT": true, + "SALES_SHIPMENT_GRID_UPDATED_AT": true, + "SALES_SHIPMENT_GRID_ORDER_CREATED_AT": true, + "SALES_SHIPMENT_GRID_SHIPPING_NAME": true, + "SALES_SHIPMENT_GRID_BILLING_NAME": true, + "FTI_086B40C8955F167B8EA76653437879B4": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPMENT_GRID_INCREMENT_ID_STORE_ID": true + } + }, + "sales_shipment_item": { + "column": { + "entity_id": true, + "parent_id": true, + "row_total": true, + "price": true, + "weight": true, + "qty": true, + "product_id": true, + "order_item_id": true, + "additional_data": true, + "description": true, + "name": true, + "sku": true + }, + "index": { + "SALES_SHIPMENT_ITEM_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPMENT_ITEM_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true + } + }, + "sales_shipment_track": { + "column": { + "entity_id": true, + "parent_id": true, + "weight": true, + "qty": true, + "order_id": true, + "track_number": true, + "description": true, + "title": true, + "carrier_code": true, + "created_at": true, + "updated_at": true + }, + "index": { + "SALES_SHIPMENT_TRACK_PARENT_ID": true, + "SALES_SHIPMENT_TRACK_ORDER_ID": true, + "SALES_SHIPMENT_TRACK_CREATED_AT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPMENT_TRACK_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true + } + }, + "sales_shipment_comment": { + "column": { + "entity_id": true, + "parent_id": true, + "is_customer_notified": true, + "is_visible_on_front": true, + "comment": true, + "created_at": true + }, + "index": { + "SALES_SHIPMENT_COMMENT_CREATED_AT": true, + "SALES_SHIPMENT_COMMENT_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPMENT_COMMENT_PARENT_ID_SALES_SHIPMENT_ENTITY_ID": true + } + }, + "sales_invoice": { + "column": { + "entity_id": true, + "store_id": true, + "base_grand_total": true, + "shipping_tax_amount": true, + "tax_amount": true, + "base_tax_amount": true, + "store_to_order_rate": true, + "base_shipping_tax_amount": true, + "base_discount_amount": true, + "base_to_order_rate": true, + "grand_total": true, + "shipping_amount": true, + "subtotal_incl_tax": true, + "base_subtotal_incl_tax": true, + "store_to_base_rate": true, + "base_shipping_amount": true, + "total_qty": true, + "base_to_global_rate": true, + "subtotal": true, + "base_subtotal": true, + "discount_amount": true, + "billing_address_id": true, + "is_used_for_refund": true, + "order_id": true, + "email_sent": true, + "send_email": true, + "can_void_flag": true, + "state": true, + "shipping_address_id": true, + "store_currency_code": true, + "transaction_id": true, + "order_currency_code": true, + "base_currency_code": true, + "global_currency_code": true, + "increment_id": true, + "created_at": true, + "updated_at": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "shipping_discount_tax_compensation_amount": true, + "base_shipping_discount_tax_compensation_amnt": true, + "shipping_incl_tax": true, + "base_shipping_incl_tax": true, + "base_total_refunded": true, + "discount_description": true, + "customer_note": true, + "customer_note_notify": true + }, + "index": { + "SALES_INVOICE_STORE_ID": true, + "SALES_INVOICE_GRAND_TOTAL": true, + "SALES_INVOICE_ORDER_ID": true, + "SALES_INVOICE_STATE": true, + "SALES_INVOICE_CREATED_AT": true, + "SALES_INVOICE_UPDATED_AT": true, + "SALES_INVOICE_SEND_EMAIL": true, + "SALES_INVOICE_EMAIL_SENT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICE_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "SALES_INVOICE_STORE_ID_STORE_STORE_ID": true, + "SALES_INVOICE_INCREMENT_ID_STORE_ID": true + } + }, + "sales_invoice_grid": { + "column": { + "entity_id": true, + "increment_id": true, + "state": true, + "store_id": true, + "store_name": true, + "order_id": true, + "order_increment_id": true, + "order_created_at": true, + "customer_name": true, + "customer_email": true, + "customer_group_id": true, + "payment_method": true, + "store_currency_code": true, + "order_currency_code": true, + "base_currency_code": true, + "global_currency_code": true, + "billing_name": true, + "billing_address": true, + "shipping_address": true, + "shipping_information": true, + "subtotal": true, + "shipping_and_handling": true, + "grand_total": true, + "created_at": true, + "updated_at": true, + "base_grand_total": true + }, + "index": { + "SALES_INVOICE_GRID_STORE_ID": true, + "SALES_INVOICE_GRID_GRAND_TOTAL": true, + "SALES_INVOICE_GRID_ORDER_ID": true, + "SALES_INVOICE_GRID_STATE": true, + "SALES_INVOICE_GRID_ORDER_INCREMENT_ID": true, + "SALES_INVOICE_GRID_CREATED_AT": true, + "SALES_INVOICE_GRID_UPDATED_AT": true, + "SALES_INVOICE_GRID_ORDER_CREATED_AT": true, + "SALES_INVOICE_GRID_BILLING_NAME": true, + "FTI_95D9C924DD6A8734EB8B5D01D60F90DE": true, + "SALES_INVOICE_GRID_BASE_GRAND_TOTAL": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICE_GRID_INCREMENT_ID_STORE_ID": true + } + }, + "sales_invoice_item": { + "column": { + "entity_id": true, + "parent_id": true, + "base_price": true, + "tax_amount": true, + "base_row_total": true, + "discount_amount": true, + "row_total": true, + "base_discount_amount": true, + "price_incl_tax": true, + "base_tax_amount": true, + "base_price_incl_tax": true, + "qty": true, + "base_cost": true, + "price": true, + "base_row_total_incl_tax": true, + "row_total_incl_tax": true, + "product_id": true, + "order_item_id": true, + "additional_data": true, + "description": true, + "sku": true, + "name": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "tax_ratio": true + }, + "index": { + "SALES_INVOICE_ITEM_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICE_ITEM_PARENT_ID_SALES_INVOICE_ENTITY_ID": true + } + }, + "sales_invoice_comment": { + "column": { + "entity_id": true, + "parent_id": true, + "is_customer_notified": true, + "is_visible_on_front": true, + "comment": true, + "created_at": true + }, + "index": { + "SALES_INVOICE_COMMENT_CREATED_AT": true, + "SALES_INVOICE_COMMENT_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICE_COMMENT_PARENT_ID_SALES_INVOICE_ENTITY_ID": true + } + }, + "sales_creditmemo": { + "column": { + "entity_id": true, + "store_id": true, + "adjustment_positive": true, + "base_shipping_tax_amount": true, + "store_to_order_rate": true, + "base_discount_amount": true, + "base_to_order_rate": true, + "grand_total": true, + "base_adjustment_negative": true, + "base_subtotal_incl_tax": true, + "shipping_amount": true, + "subtotal_incl_tax": true, + "adjustment_negative": true, + "base_shipping_amount": true, + "store_to_base_rate": true, + "base_to_global_rate": true, + "base_adjustment": true, + "base_subtotal": true, + "discount_amount": true, + "subtotal": true, + "adjustment": true, + "base_grand_total": true, + "base_adjustment_positive": true, + "base_tax_amount": true, + "shipping_tax_amount": true, + "tax_amount": true, + "order_id": true, + "email_sent": true, + "send_email": true, + "creditmemo_status": true, + "state": true, + "shipping_address_id": true, + "billing_address_id": true, + "invoice_id": true, + "store_currency_code": true, + "order_currency_code": true, + "base_currency_code": true, + "global_currency_code": true, + "transaction_id": true, + "increment_id": true, + "created_at": true, + "updated_at": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "shipping_discount_tax_compensation_amount": true, + "base_shipping_discount_tax_compensation_amnt": true, + "shipping_incl_tax": true, + "base_shipping_incl_tax": true, + "discount_description": true, + "customer_note": true, + "customer_note_notify": true + }, + "index": { + "SALES_CREDITMEMO_STORE_ID": true, + "SALES_CREDITMEMO_ORDER_ID": true, + "SALES_CREDITMEMO_CREDITMEMO_STATUS": true, + "SALES_CREDITMEMO_STATE": true, + "SALES_CREDITMEMO_CREATED_AT": true, + "SALES_CREDITMEMO_UPDATED_AT": true, + "SALES_CREDITMEMO_SEND_EMAIL": true, + "SALES_CREDITMEMO_EMAIL_SENT": true + }, + "constraint": { + "PRIMARY": true, + "SALES_CREDITMEMO_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "SALES_CREDITMEMO_STORE_ID_STORE_STORE_ID": true, + "SALES_CREDITMEMO_INCREMENT_ID_STORE_ID": true + } + }, + "sales_creditmemo_grid": { + "column": { + "entity_id": true, + "increment_id": true, + "created_at": true, + "updated_at": true, + "order_id": true, + "order_increment_id": true, + "order_created_at": true, + "billing_name": true, + "state": true, + "base_grand_total": true, + "order_status": true, + "store_id": true, + "billing_address": true, + "shipping_address": true, + "customer_name": true, + "customer_email": true, + "customer_group_id": true, + "payment_method": true, + "shipping_information": true, + "subtotal": true, + "shipping_and_handling": true, + "adjustment_positive": true, + "adjustment_negative": true, + "order_base_grand_total": true + }, + "index": { + "SALES_CREDITMEMO_GRID_ORDER_INCREMENT_ID": true, + "SALES_CREDITMEMO_GRID_CREATED_AT": true, + "SALES_CREDITMEMO_GRID_UPDATED_AT": true, + "SALES_CREDITMEMO_GRID_ORDER_CREATED_AT": true, + "SALES_CREDITMEMO_GRID_STATE": true, + "SALES_CREDITMEMO_GRID_BILLING_NAME": true, + "SALES_CREDITMEMO_GRID_ORDER_STATUS": true, + "SALES_CREDITMEMO_GRID_BASE_GRAND_TOTAL": true, + "SALES_CREDITMEMO_GRID_STORE_ID": true, + "SALES_CREDITMEMO_GRID_ORDER_BASE_GRAND_TOTAL": true, + "SALES_CREDITMEMO_GRID_ORDER_ID": true, + "FTI_32B7BA885941A8254EE84AE650ABDC86": true + }, + "constraint": { + "PRIMARY": true, + "SALES_CREDITMEMO_GRID_INCREMENT_ID_STORE_ID": true + } + }, + "sales_creditmemo_item": { + "column": { + "entity_id": true, + "parent_id": true, + "base_price": true, + "tax_amount": true, + "base_row_total": true, + "discount_amount": true, + "row_total": true, + "base_discount_amount": true, + "price_incl_tax": true, + "base_tax_amount": true, + "base_price_incl_tax": true, + "qty": true, + "base_cost": true, + "price": true, + "base_row_total_incl_tax": true, + "row_total_incl_tax": true, + "product_id": true, + "order_item_id": true, + "additional_data": true, + "description": true, + "sku": true, + "name": true, + "discount_tax_compensation_amount": true, + "base_discount_tax_compensation_amount": true, + "tax_ratio": true + }, + "index": { + "SALES_CREDITMEMO_ITEM_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_CREDITMEMO_ITEM_PARENT_ID_SALES_CREDITMEMO_ENTITY_ID": true + } + }, + "sales_creditmemo_comment": { + "column": { + "entity_id": true, + "parent_id": true, + "is_customer_notified": true, + "is_visible_on_front": true, + "comment": true, + "created_at": true + }, + "index": { + "SALES_CREDITMEMO_COMMENT_CREATED_AT": true, + "SALES_CREDITMEMO_COMMENT_PARENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_CREDITMEMO_COMMENT_PARENT_ID_SALES_CREDITMEMO_ENTITY_ID": true + } + }, + "sales_invoiced_aggregated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "orders_invoiced": true, + "invoiced": true, + "invoiced_captured": true, + "invoiced_not_captured": true + }, + "index": { + "SALES_INVOICED_AGGREGATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICED_AGGREGATED_STORE_ID_STORE_STORE_ID": true, + "SALES_INVOICED_AGGREGATED_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_invoiced_aggregated_order": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "orders_invoiced": true, + "invoiced": true, + "invoiced_captured": true, + "invoiced_not_captured": true + }, + "index": { + "SALES_INVOICED_AGGREGATED_ORDER_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_INVOICED_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, + "SALES_INVOICED_AGGREGATED_ORDER_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_order_aggregated_created": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "total_qty_ordered": true, + "total_qty_invoiced": true, + "total_income_amount": true, + "total_revenue_amount": true, + "total_profit_amount": true, + "total_invoiced_amount": true, + "total_canceled_amount": true, + "total_paid_amount": true, + "total_refunded_amount": true, + "total_tax_amount": true, + "total_tax_amount_actual": true, + "total_shipping_amount": true, + "total_shipping_amount_actual": true, + "total_discount_amount": true, + "total_discount_amount_actual": true + }, + "index": { + "SALES_ORDER_AGGREGATED_CREATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_AGGREGATED_CREATED_STORE_ID_STORE_STORE_ID": true, + "SALES_ORDER_AGGREGATED_CREATED_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_order_aggregated_updated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "total_qty_ordered": true, + "total_qty_invoiced": true, + "total_income_amount": true, + "total_revenue_amount": true, + "total_profit_amount": true, + "total_invoiced_amount": true, + "total_canceled_amount": true, + "total_paid_amount": true, + "total_refunded_amount": true, + "total_tax_amount": true, + "total_tax_amount_actual": true, + "total_shipping_amount": true, + "total_shipping_amount_actual": true, + "total_discount_amount": true, + "total_discount_amount_actual": true + }, + "index": { + "SALES_ORDER_AGGREGATED_UPDATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, + "SALES_ORDER_AGGREGATED_UPDATED_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_payment_transaction": { + "column": { + "transaction_id": true, + "parent_id": true, + "order_id": true, + "payment_id": true, + "txn_id": true, + "parent_txn_id": true, + "txn_type": true, + "is_closed": true, + "additional_information": true, + "created_at": true + }, + "index": { + "SALES_PAYMENT_TRANSACTION_PARENT_ID": true, + "SALES_PAYMENT_TRANSACTION_PAYMENT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_PAYMENT_TRANSACTION_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "FK_B99FF1A06402D725EBDB0F3A7ECD47A2": true, + "SALES_PAYMENT_TRANSACTION_PAYMENT_ID_SALES_ORDER_PAYMENT_ENTT_ID": true, + "SALES_PAYMENT_TRANSACTION_ORDER_ID_PAYMENT_ID_TXN_ID": true + } + }, + "sales_refunded_aggregated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "refunded": true, + "online_refunded": true, + "offline_refunded": true + }, + "index": { + "SALES_REFUNDED_AGGREGATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_REFUNDED_AGGREGATED_STORE_ID_STORE_STORE_ID": true, + "SALES_REFUNDED_AGGREGATED_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_refunded_aggregated_order": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "orders_count": true, + "refunded": true, + "online_refunded": true, + "offline_refunded": true + }, + "index": { + "SALES_REFUNDED_AGGREGATED_ORDER_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_REFUNDED_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, + "SALES_REFUNDED_AGGREGATED_ORDER_PERIOD_STORE_ID_ORDER_STATUS": true + } + }, + "sales_shipping_aggregated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "shipping_description": true, + "orders_count": true, + "total_shipping": true, + "total_shipping_actual": true + }, + "index": { + "SALES_SHIPPING_AGGREGATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPPING_AGGREGATED_STORE_ID_STORE_STORE_ID": true, + "SALES_SHPP_AGGRED_PERIOD_STORE_ID_ORDER_STS_SHPP_DESCRIPTION": true + } + }, + "sales_shipping_aggregated_order": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "shipping_description": true, + "orders_count": true, + "total_shipping": true, + "total_shipping_actual": true + }, + "index": { + "SALES_SHIPPING_AGGREGATED_ORDER_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SHIPPING_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, + "UNQ_C05FAE47282EEA68654D0924E946761F": true + } + }, + "sales_bestsellers_aggregated_daily": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "qty_ordered": true, + "rating_pos": true + }, + "index": { + "SALES_BESTSELLERS_AGGREGATED_DAILY_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_DAILY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_BESTSELLERS_AGGREGATED_DAILY_STORE_ID_STORE_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_DAILY_PERIOD_STORE_ID_PRODUCT_ID": true + } + }, + "sales_bestsellers_aggregated_monthly": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "qty_ordered": true, + "rating_pos": true + }, + "index": { + "SALES_BESTSELLERS_AGGREGATED_MONTHLY_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_MONTHLY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_BESTSELLERS_AGGREGATED_MONTHLY_STORE_ID_STORE_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_MONTHLY_PERIOD_STORE_ID_PRODUCT_ID": true + } + }, + "sales_bestsellers_aggregated_yearly": { + "column": { + "id": true, + "period": true, + "store_id": true, + "product_id": true, + "product_name": true, + "product_price": true, + "qty_ordered": true, + "rating_pos": true + }, + "index": { + "SALES_BESTSELLERS_AGGREGATED_YEARLY_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_YEARLY_PRODUCT_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_BESTSELLERS_AGGREGATED_YEARLY_STORE_ID_STORE_STORE_ID": true, + "SALES_BESTSELLERS_AGGREGATED_YEARLY_PERIOD_STORE_ID_PRODUCT_ID": true + } + }, + "sales_order_tax": { + "column": { + "tax_id": true, + "order_id": true, + "code": true, + "title": true, + "percent": true, + "amount": true, + "priority": true, + "position": true, + "base_amount": true, + "process": true, + "base_real_amount": true + }, + "index": { + "SALES_ORDER_TAX_ORDER_ID_PRIORITY_POSITION": true + }, + "constraint": { + "PRIMARY": true + } + }, + "sales_order_tax_item": { + "column": { + "tax_item_id": true, + "tax_id": true, + "item_id": true, + "tax_percent": true, + "amount": true, + "base_amount": true, + "real_amount": true, + "real_base_amount": true, + "associated_item_id": true, + "taxable_item_type": true + }, + "index": { + "SALES_ORDER_TAX_ITEM_ITEM_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_TAX_ITEM_ASSOCIATED_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true, + "SALES_ORDER_TAX_ITEM_TAX_ID_SALES_ORDER_TAX_TAX_ID": true, + "SALES_ORDER_TAX_ITEM_ITEM_ID_SALES_ORDER_ITEM_ITEM_ID": true, + "SALES_ORDER_TAX_ITEM_TAX_ID_ITEM_ID": true + } + }, + "sales_order_status": { + "column": { + "status": true, + "label": true + }, + "constraint": { + "PRIMARY": true + } + }, + "sales_order_status_state": { + "column": { + "status": true, + "state": true, + "is_default": true, + "visible_on_front": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_STATUS_STATE_STATUS_SALES_ORDER_STATUS_STATUS": true + } + }, + "sales_order_status_label": { + "column": { + "status": true, + "store_id": true, + "label": true + }, + "index": { + "SALES_ORDER_STATUS_LABEL_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALES_ORDER_STATUS_LABEL_STATUS_SALES_ORDER_STATUS_STATUS": true, + "SALES_ORDER_STATUS_LABEL_STORE_ID_STORE_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 0f69389c2969e..be64ce5c84a35 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -354,43 +354,43 @@ <virtualType name="SalesOrderSendEmailsObserver" type="Magento\Sales\Observer\Virtual\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesOrderInvoiceSendEmailsObserver" type="Magento\Sales\Observer\Virtual\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderInvoiceSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderInvoiceSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesOrderShipmentSendEmailsObserver" type="Magento\Sales\Observer\Virtual\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderShipmentSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderShipmentSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesOrderCreditmemoSendEmailsObserver" type="Magento\Sales\Observer\Virtual\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderCreditmemoSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderCreditmemoSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesOrderSendEmailsCron" type="Magento\Sales\Cron\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesInvoiceSendEmailsCron" type="Magento\Sales\Cron\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderInvoiceSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderInvoiceSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesShipmentSendEmailsCron" type="Magento\Sales\Cron\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderShipmentSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderShipmentSendEmails</argument> </arguments> </virtualType> <virtualType name="SalesCreditmemoSendEmailsCron" type="Magento\Sales\Cron\SendEmails"> <arguments> - <argument name="emailSenderHandler" xsi:type="object">SalesOrderCreditmemoSendEmails</argument> + <argument name="emailSenderHandler" xsi:type="object" shared="false">SalesOrderCreditmemoSendEmails</argument> </arguments> </virtualType> <type name="Magento\SalesSequence\Model\EntityPool"> @@ -980,6 +980,13 @@ <type name="Magento\Sales\Model\ResourceModel\Order\Handler\Address"> <plugin name="addressUpdate" type="Magento\Sales\Model\Order\Invoice\Plugin\AddressUpdate"/> </type> + <type name="Magento\Framework\Console\CommandListInterface"> + <arguments> + <argument name="commands" xsi:type="array"> + <item name="encyption_payment_data_update" xsi:type="object">Magento\Sales\Console\Command\EncryptionPaymentDataUpdateCommand</item> + </argument> + </arguments> + </type> <type name="Magento\Config\Model\Config\TypePool"> <arguments> <argument name="sensitive" xsi:type="array"> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_new.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_new.xml index 0c1b395b5116d..71490553aff17 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_new.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_new.xml @@ -22,7 +22,6 @@ <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="Magento_Sales::items/column/qty.phtml" group="column"/> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="Magento_Sales::items/column/name.phtml" group="column"/> <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Totalbar" name="order_totalbar" template="Magento_Sales::order/totalbar.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Creditmemo\Totals" name="creditmemo_totals" template="Magento_Sales::order/totals.phtml"> <block class="Magento\Sales\Block\Adminhtml\Order\Creditmemo\Create\Adjustments" name="adjustments" template="Magento_Sales::order/creditmemo/create/totals/adjustments.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Totals\Tax" name="tax" template="Magento_Sales::order/totals/tax.phtml"/> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml index 29a61308391c6..8375bec965794 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_creditmemo_updateqty.xml @@ -13,7 +13,6 @@ <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="Magento_Sales::items/column/qty.phtml" group="column"/> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="Magento_Sales::items/column/name.phtml" group="column"/> <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Totalbar" name="order_totalbar" template="Magento_Sales::order/totalbar.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Creditmemo\Totals" name="creditmemo_totals" template="Magento_Sales::order/totals.phtml"> <block class="Magento\Sales\Block\Adminhtml\Order\Creditmemo\Create\Adjustments" name="adjustments" template="Magento_Sales::order/creditmemo/create/totals/adjustments.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Totals\Tax" name="tax" template="Magento_Sales::order/totals/tax.phtml"/> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_new.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_new.xml index def5ebaf546cd..b589ac0ac793d 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_new.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_new.xml @@ -25,7 +25,6 @@ <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="Magento_Sales::items/column/qty.phtml" group="column"/> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="Magento_Sales::items/column/name.phtml" group="column"/> <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Totalbar" name="order_totalbar" template="Magento_Sales::order/totalbar.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Invoice\Totals" name="invoice_totals" template="Magento_Sales::order/totals.phtml"> <block class="Magento\Sales\Block\Adminhtml\Order\Totals\Tax" name="tax" template="Magento_Sales::order/totals/tax.phtml"/> </block> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_updateqty.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_updateqty.xml index 4df3f057f6a58..38e4cb50f4343 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_updateqty.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_invoice_updateqty.xml @@ -13,7 +13,6 @@ <block class="Magento\Sales\Block\Adminhtml\Items\Column\Qty" name="column_qty" template="Magento_Sales::items/column/qty.phtml" group="column"/> <block class="Magento\Sales\Block\Adminhtml\Items\Column\Name" name="column_name" template="Magento_Sales::items/column/name.phtml" group="column"/> <block class="Magento\Framework\View\Element\Text\ListText" name="order_item_extra_info"/> - <block class="Magento\Sales\Block\Adminhtml\Order\Totalbar" name="order_totalbar" template="Magento_Sales::order/totalbar.phtml"/> <block class="Magento\Sales\Block\Adminhtml\Order\Invoice\Totals" name="invoice_totals" template="Magento_Sales::order/totals.phtml"> <block class="Magento\Sales\Block\Adminhtml\Order\Totals\Tax" name="tax" template="Magento_Sales::order/totals/tax.phtml"/> </block> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml index 30037a918a10c..a903c92ef33e2 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml @@ -14,32 +14,34 @@ ?> <?php if ($_item = $block->getItem()): ?> - <div id="order_item_<?= /* @escapeNotVerified */ $_item->getId() ?>_title" + <div id="order_item_<?= $block->escapeHtml($_item->getId()) ?>_title" class="product-title"> <?= $block->escapeHtml($_item->getName()) ?> </div> - <div class="product-sku-block"> - <span><?= /* @escapeNotVerified */ __('SKU') ?>:</span> <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($block->getSku()))) ?> + <span><?= $block->escapeHtml(__('SKU'))?>:</span> <?= implode('<br />', $this->helper('Magento\Catalog\Helper\Data')->splitSku($block->escapeHtml($block->getSku()))) ?> </div> <?php if ($block->getOrderOptions()): ?> <dl class="item-options"> <?php foreach ($block->getOrderOptions() as $_option): ?> - <dt><?= /* @escapeNotVerified */ $_option['label'] ?>:</dt> + <dt><?= $block->escapeHtml($_option['label']) ?>:</dt> <dd> <?php if (isset($_option['custom_view']) && $_option['custom_view']): ?> <?= /* @escapeNotVerified */ $block->getCustomizedOptionValue($_option) ?> <?php else: ?> <?php $_option = $block->getFormattedOption($_option['value']); ?> - <?= $block->escapeHtml($_option['value']) ?><?php if (isset($_option['remainder']) && $_option['remainder']): ?><span id="<?= /* @escapeNotVerified */ $_dots = 'dots' . uniqid() ?>"> ...</span><span id="<?= /* @escapeNotVerified */ $_id = 'id' . uniqid() ?>"><?= /* @escapeNotVerified */ $_option['remainder'] ?></span> + <?php $dots = 'dots' . uniqid(); ?> + <?= $block->escapeHtml($_option['value']) ?><?php if (isset($_option['remainder']) && $_option['remainder']): ?> <span id="<?= /* @noEscape */ $dots; ?>"> ...</span> + <?php $id = 'id' . uniqid(); ?> + <span id="<?= /* @noEscape */ $id; ?>"><?= $block->escapeHtml($_option['remainder']) ?></span> <script> require(['prototype'], function() { - $('<?= /* @escapeNotVerified */ $_id ?>').hide(); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_id ?>').show();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseover', function(){$('<?= /* @escapeNotVerified */ $_dots ?>').hide();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_id ?>').hide();}); - $('<?= /* @escapeNotVerified */ $_id ?>').up().observe('mouseout', function(){$('<?= /* @escapeNotVerified */ $_dots ?>').show();}); + $('<?= /* @noEscape */ $id; ?>').hide(); + $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $id; ?>').show();}); + $('<?= /* @noEscape */ $id; ?>').up().observe('mouseover', function(){$('<?= /* @noEscape */ $dots; ?>').hide();}); + $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $id; ?>').hide();}); + $('<?= /* @noEscape */ $id; ?>').up().observe('mouseout', function(){$('<?= /* @noEscape */ $dots; ?>').show();}); }); </script> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/items/column/qty.phtml b/app/code/Magento/Sales/view/adminhtml/templates/items/column/qty.phtml index faa49dca3a8eb..9bf4856795197 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/items/column/qty.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/items/column/qty.phtml @@ -7,38 +7,38 @@ // @codingStandardsIgnoreFile ?> -<?php if ($_item = $block->getItem()): ?> +<?php if ($item = $block->getItem()): ?> <table class="qty-table"> <tr> - <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyOrdered()*1 ?></td> + <th><?= $block->escapeHtml(__('Ordered')); ?></th> + <td><?= /* @noEscape */ $item->getQtyOrdered()*1 ?></td> </tr> - <?php if ((float) $_item->getQtyInvoiced()): ?> + <?php if ((float) $item->getQtyInvoiced()): ?> <tr> - <th><?= /* @escapeNotVerified */ __('Invoiced') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyInvoiced()*1 ?></td> + <th><?= $block->escapeHtml(__('Invoiced')); ?></th> + <td><?= /* @noEscape */ $item->getQtyInvoiced()*1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyShipped()): ?> + <?php if ((float) $item->getQtyShipped()): ?> <tr> - <th><?= /* @escapeNotVerified */ __('Shipped') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?></td> + <th><?= $block->escapeHtml(__('Shipped')); ?></th> + <td><?= /* @noEscape */ $item->getQtyShipped()*1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyRefunded()): ?> + <?php if ((float) $item->getQtyRefunded()): ?> <tr> - <th><?= /* @escapeNotVerified */ __('Refunded') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyRefunded()*1 ?></td> + <th><?= $block->escapeHtml(__('Refunded')); ?></th> + <td><?= /* @noEscape */ $item->getQtyRefunded()*1 ?></td> </tr> <?php endif; ?> - <?php if ((float) $_item->getQtyCanceled()): ?> + <?php if ((float) $item->getQtyCanceled()): ?> <tr> - <th><?= /* @escapeNotVerified */ __('Canceled') ?></th> - <td><?= /* @escapeNotVerified */ $_item->getQtyCanceled()*1 ?></td> + <th><?= $block->escapeHtml(__('Canceled')); ?></th> + <td><?= /* @noEscape */ $item->getQtyCanceled()*1 ?></td> </tr> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/totalbar.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/totalbar.phtml index b038c72199c7f..14025eb2dd51a 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/totalbar.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/totalbar.phtml @@ -4,16 +4,18 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile +$totals = $block->getTotals(); ?> -<?php if (sizeof($block->getTotals()) > 0): ?> +<?php if ($totals && count($totals) > 0): ?> <table class="items-to-invoice"> <tr> - <?php foreach ($block->getTotals() as $_total): ?> - <td <?php if ($_total['grand']): ?>class="grand-total"<?php endif; ?>> - <?= /* @escapeNotVerified */ $_total['label'] ?><br /> - <?= /* @escapeNotVerified */ $_total['value'] ?> + <?php foreach ($totals as $total): ?> + <td <?php if ($total['grand']): ?>class="grand-total"<?php endif; ?>> + <?= $block->escapeHtml($total['label']) ?><br /> + <?= $block->escapeHtml($total['value']) ?> </td> <?php endforeach; ?> </tr> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/totals.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/totals.phtml index d23ebac84e6c2..27b82505a11d3 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/totals.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/totals.phtml @@ -7,14 +7,6 @@ // @codingStandardsIgnoreFile ?> -<?php /*$_source = $block->getSource(); ?> -<?php $block->setPriceDataObject($_source) ?> -<?php if ($_source): ?> -<table width="100%"> - <?= $block->getChildHtml('main') ?> - <?= $block->getChildHtml('footer') ?> -</table> -<?php endif;*/ ?> <table class="data-table admin__table-secondary order-subtotal-table"> <?php $_totals = $block->getTotals('footer')?> diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index 8990ea8d3f75b..53ab6c204fa5b 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -21,6 +21,7 @@ define([ this.loadBaseUrl = false; this.customerId = data.customer_id ? data.customer_id : false; this.storeId = data.store_id ? data.store_id : false; + this.quoteId = data['quote_id'] ? data['quote_id'] : false; this.currencyId = false; this.currencySymbol = data.currency_symbol ? data.currency_symbol : ''; this.addresses = data.addresses ? data.addresses : $H({}); @@ -38,6 +39,7 @@ define([ this.isOnlyVirtualProduct = false; this.excludedPaymentMethods = []; this.summarizePrice = true; + this.timerId = null; jQuery.async('#order-items', (function(){ this.dataArea = new OrderFormArea('data', $(this.getAreaId('data')), this); this.itemsArea = Object.extend(new OrderFormArea('items', $(this.getAreaId('items')), this), { @@ -47,7 +49,7 @@ define([ var buttons = controlButtonArea.childElements(); for (var i = 0; i < buttons.length; i++) { if (buttons[i].innerHTML.include(button.label)) { - return ; + return; } } button.insertIn(controlButtonArea, 'top'); @@ -188,14 +190,27 @@ define([ bindAddressFields : function(container) { var fields = $(container).select('input', 'select', 'textarea'); for(var i=0;i<fields.length;i++){ - Event.observe(fields[i], 'change', this.changeAddressField.bind(this)); + Event.observe(fields[i], 'change', this.triggerChangeEvent.bind(this)); } }, + /** + * Calls changing address field handler after timeout to prevent multiple simultaneous calls. + * + * @param {Event} event + */ + triggerChangeEvent: function (event) { + if (this.timerId) { + window.clearTimeout(this.timerId); + } + + this.timerId = window.setTimeout(this.changeAddressField.bind(this), 500, event); + }, + /** * Triggers on each form's element changes. * - * @param {Object} event + * @param {Event} event */ changeAddressField: function (event) { var field = Event.element(event), @@ -618,7 +633,7 @@ define([ } else if (((elms[i].type == 'checkbox' || elms[i].type == 'radio') && elms[i].checked) || ((elms[i].type == 'file' || elms[i].type == 'text' || elms[i].type == 'textarea' || elms[i].type == 'hidden') - && Form.Element.getValue(elms[i])) + && Form.Element.getValue(elms[i])) ) { if (this._isSummarizePrice(elms[i])) { productPrice += getPrice(elms[i]); @@ -1130,7 +1145,7 @@ define([ */ isPaymentValidationAvailable : function(){ return ((typeof this.paymentMethod) == 'undefined' - || this.excludedPaymentMethods.indexOf(this.paymentMethod) == -1); + || this.excludedPaymentMethods.indexOf(this.paymentMethod) == -1); }, serializeData : function(container){ diff --git a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml index 20c2c1869fedb..1fca65932b0b0 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/items/creditmemo/default.phtml @@ -31,6 +31,6 @@ </td> <td class="item-qty"><?= /* @escapeNotVerified */ $_item->getQty() * 1 ?></td> <td class="item-price"> - <?= /* @escapeNotVerified */ $block->getItemPrice($_item) ?> + <?= /* @escapeNotVerified */ $block->getItemPrice($_item->getOrderItem()) ?> </td> </tr> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/items/renderer/default.phtml b/app/code/Magento/Sales/view/frontend/templates/order/items/renderer/default.phtml index 227866b8e1c3d..19da4994c914e 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/items/renderer/default.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/items/renderer/default.phtml @@ -20,9 +20,9 @@ $_item = $block->getItem(); <?php $_formatedOptionValue = $block->getFormatedOptionValue($_option) ?> <dd> <?php if (isset($_formatedOptionValue['full_view'])): ?> - <?= $block->escapeHtml($_formatedOptionValue['full_view']) ?> + <?= $block->escapeHtml($_formatedOptionValue['full_view'], ['a']) ?> <?php else: ?> - <?=$block->escapeHtml($_formatedOptionValue['value']) ?> + <?=$block->escapeHtml($_formatedOptionValue['value'], ['a']) ?> <?php endif; ?> </dd> <?php else: ?> diff --git a/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json b/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 7382a7dcc8e5a..0000000000000 --- a/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-sales": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesInventory/Test/Mftf/composer.json b/app/code/Magento/SalesInventory/Test/Mftf/composer.json deleted file mode 100644 index 7a72dfc649780..0000000000000 --- a/app/code/Magento/SalesInventory/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-inventory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php index 9adb62583985d..28993e159499b 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Delete.php @@ -21,13 +21,13 @@ public function execute() $model = $this->_objectManager->create(\Magento\SalesRule\Model\Rule::class); $model->load($id); $model->delete(); - $this->messageManager->addSuccess(__('You deleted the rule.')); + $this->messageManager->addSuccessMessage(__('You deleted the rule.')); $this->_redirect('sales_rule/*/'); return; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('We can\'t delete the rule right now. Please review the log and try again.') ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); @@ -35,7 +35,7 @@ public function execute() return; } } - $this->messageManager->addError(__('We can\'t find a rule to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a rule to delete.')); $this->_redirect('sales_rule/*/'); } } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php index 7221f49b852d3..717c71fde4837 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Edit.php @@ -51,7 +51,7 @@ public function execute() if ($id) { $model->load($id); if (!$model->getRuleId()) { - $this->messageManager->addError(__('This rule no longer exists.')); + $this->messageManager->addErrorMessage(__('This rule no longer exists.')); $this->_redirect('sales_rule/*'); return; } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php index 297db7d96939d..12d34b8320d07 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php @@ -64,7 +64,7 @@ public function execute() $couponCodes = $this->couponGenerator->generateCodes($data); $generated = count($couponCodes); - $this->messageManager->addSuccess(__('%1 coupon(s) have been generated.', $generated)); + $this->messageManager->addSuccessMessage(__('%1 coupon(s) have been generated.', $generated)); $this->_view->getLayout()->initMessages(); $result['messages'] = $this->_view->getLayout()->getMessagesBlock()->getGroupedHtml(); } catch (\Magento\Framework\Exception\InputException $inputException) { diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php index f28b245ee04ab..a5b71130e70eb 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php @@ -50,7 +50,7 @@ public function execute() $validateResult = $model->validateData(new \Magento\Framework\DataObject($data)); if ($validateResult !== true) { foreach ($validateResult as $errorMessage) { - $this->messageManager->addError($errorMessage); + $this->messageManager->addErrorMessage($errorMessage); } $session->setPageData($data); $this->_redirect('sales_rule/*/edit', ['id' => $model->getId()]); @@ -82,7 +82,7 @@ public function execute() $session->setPageData($model->getData()); $model->save(); - $this->messageManager->addSuccess(__('You saved the rule.')); + $this->messageManager->addSuccessMessage(__('You saved the rule.')); $session->setPageData(false); if ($this->getRequest()->getParam('back')) { $this->_redirect('sales_rule/*/edit', ['id' => $model->getId()]); @@ -91,7 +91,7 @@ public function execute() $this->_redirect('sales_rule/*/'); return; } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $id = (int)$this->getRequest()->getParam('rule_id'); if (!empty($id)) { $this->_redirect('sales_rule/*/edit', ['id' => $id]); @@ -100,7 +100,7 @@ public function execute() } return; } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Something went wrong while saving the rule data. Please review the error log.') ); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php index 794fc94d6a2a8..3a5ed16fdd2fd 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule.php @@ -239,7 +239,7 @@ public function saveStoreLabels($ruleId, $labels) $connection->delete($table, ['rule_id=?' => $ruleId, 'store_id IN (?)' => $deleteByStoreIds]); } } catch (\Exception $e) { - $connection->rollback(); + $connection->rollBack(); throw $e; } $connection->commit(); diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php index 54b50dbdf38db..59f24fa8b6e03 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php @@ -6,6 +6,7 @@ namespace Magento\SalesRule\Model\ResourceModel\Rule; +use Magento\Framework\DB\Select; use Magento\Framework\Serialize\Serializer\Json; use Magento\Quote\Model\Quote\Address; @@ -209,7 +210,9 @@ public function setValidationFilter( $andWhereCondition = implode(' AND ', $andWhereConditions); $select->where( - $noCouponWhereCondition . ' OR ((' . $orWhereCondition . ') AND ' . $andWhereCondition . ')' + $noCouponWhereCondition . ' OR ((' . $orWhereCondition . ') AND ' . $andWhereCondition . ')', + null, + Select::TYPE_CONDITION ); } else { $this->addFieldToFilter( @@ -320,7 +323,7 @@ public function addAttributeInConditionFilter($attributeCode) $this->getSelect()->where( sprintf('(%s OR %s)', $cCond, $aCond), null, - \Magento\Framework\DB\Select::TYPE_CONDITION + Select::TYPE_CONDITION ); return $this; diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php index 499e35db9dfd6..d2e7cabe473f4 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\SalesRule\Model\Rule\Condition; /** @@ -51,10 +52,17 @@ public function validate(\Magento\Framework\Model\AbstractModel $model) $attrCode = $this->getAttribute(); - if ('category_ids' == $attrCode) { + if ($attrCode === 'category_ids') { return $this->validateAttribute($this->_getAvailableInCategories($product->getId())); } + if ($attrCode === 'quote_item_price') { + $numericOperations = $this->getDefaultOperatorInputByType()['numeric']; + if (in_array($this->getOperator(), $numericOperations)) { + $this->setData('value', $this->getFormattedPrice($this->getValue())); + } + } + return parent::validate($product); } @@ -79,4 +87,23 @@ public function getValueElementChooserUrl() } return $url !== false ? $this->_backendData->getUrl($url) : ''; } + + /** + * @param string $value + * @return float|null + */ + private function getFormattedPrice($value) + { + $value = preg_replace('/[^0-9^\^.,-]/m', '', $value); + + /** + * If the comma is the third symbol in the number, we consider it to be a decimal separator + */ + $separatorComa = strpos($value, ','); + $separatorDot = strpos($value, '.'); + if ($separatorComa !== false && $separatorDot === false && preg_match('/,\d{3}$/m', $value) === 1) { + $value .= '.00'; + } + return $this->_localeFormat->getNumber($value); + } } diff --git a/app/code/Magento/SalesRule/Model/Utility.php b/app/code/Magento/SalesRule/Model/Utility.php index a3876a9d7e046..45c5dc3da0ebd 100644 --- a/app/code/Magento/SalesRule/Model/Utility.php +++ b/app/code/Magento/SalesRule/Model/Utility.php @@ -189,6 +189,8 @@ public function deltaRoundingFix( ) { $discountAmount = $discountData->getAmount(); $baseDiscountAmount = $discountData->getBaseAmount(); + $rowTotalInclTax = $item->getRowTotalInclTax(); + $baseRowTotalInclTax = $item->getBaseRowTotalInclTax(); //TODO Seems \Magento\Quote\Model\Quote\Item\AbstractItem::getDiscountPercent() returns float value //that can not be used as array index @@ -205,6 +207,23 @@ public function deltaRoundingFix( - $this->priceCurrency->round($baseDiscountAmount); } + /** + * When we have 100% discount check if totals will not be negative + */ + + if ($percentKey == 100) { + $discountDelta = $rowTotalInclTax - $discountAmount; + $baseDiscountDelta = $baseRowTotalInclTax - $baseDiscountAmount; + + if ($discountDelta < 0) { + $discountAmount += $discountDelta; + } + + if ($baseDiscountDelta < 0) { + $baseDiscountAmount += $baseDiscountDelta; + } + } + $discountData->setAmount($this->priceCurrency->round($discountAmount)); $discountData->setBaseAmount($this->priceCurrency->round($baseDiscountAmount)); diff --git a/app/code/Magento/SalesRule/Observer/CheckSalesRulesAvailability.php b/app/code/Magento/SalesRule/Observer/CheckSalesRulesAvailability.php index 4b66d2b98642c..f2819a06fe1bf 100644 --- a/app/code/Magento/SalesRule/Observer/CheckSalesRulesAvailability.php +++ b/app/code/Magento/SalesRule/Observer/CheckSalesRulesAvailability.php @@ -54,7 +54,7 @@ public function checkSalesRulesAvailability($attributeCode) } if ($disabledRulesCount) { - $this->messageManager->addWarning( + $this->messageManager->addWarningMessage( __( '%1 Cart Price Rules based on "%2" attribute have been disabled.', $disabledRulesCount, diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml index 997b3b4b9ff06..bae7069859937 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="selectNotLoggedInCustomerGroup"> <!-- This actionGroup was created to be merged from B2B because B2B has a very different form control here --> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="NOT LOGGED IN" stepKey="selectCustomerGroup"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminSalesRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminSalesRuleActionGroup.xml index 4026c3d65cfaf..800621ac70ff1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminSalesRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminSalesRuleActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteCartPriceRuleByName"> <arguments> <argument name="ruleName" type="string"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml index e8c520f1f985e..55b2e9c10fd64 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="ApplyCartRuleOnStorefrontActionGroup"> <arguments> <argument name="Product" defaultValue="_defaultProduct"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml index 5d1cc877aa775..70d1fc56d2cea 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Apply Sales Rule Coupon to the cart --> <actionGroup name="StorefrontApplyCouponActionGroup"> <arguments> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/QuoteData.xml index 7e929266b89ef..4cd5513080f73 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/QuoteData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/QuoteData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- @TODO: Get rid off this workaround and its usages after MQE-498 is implemented --> <entity name="E2EB2CQuoteWith10PercentDiscount" type="Quote"> <data key="subtotal">480.00</data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesCouponData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesCouponData.xml index 917b4ca179b8a..bab82fa20463b 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesCouponData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesCouponData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ApiSalesRuleCoupon" type="SalesRuleCoupon"> <data key="code" unique="suffix">salesCoupon</data> <data key="times_used">0</data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml index 6cca73dff5724..10b198b53f389 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleCouponData.xml @@ -6,7 +6,7 @@ */ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SimpleSalesRuleCoupon" type="SalesRuleCoupon"> <var key="rule_id" entityKey="rule_id" entityType="SalesRule"/> <data key="code" unique="suffix">Code</data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index efd21405a7cc1..5b7585b8b2a3f 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ApiSalesRule" type="SalesRule"> <data key="name" unique="suffix">salesRule</data> <data key="description">Sales Rule Descritpion</data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleLabelData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleLabelData.xml index f1916e81e0e52..90fe36fd7bdba 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleLabelData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleLabelData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SalesRuleLabelDefault" type="SalesRuleLabel"> <data key="store_id">0</data> <data key="store_label" unique="suffix">Sales Rule (Default) </data> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-condition-meta.xml b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-condition-meta.xml index bf4809a7d3f8d..bd50be31e01b8 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-condition-meta.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-condition-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateSalesRuleCondition" dataType="SalesRuleCondition" type="create"> <field key="condition_type" required="true">string</field> <array key="conditions" required="true"> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-coupon-meta.xml b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-coupon-meta.xml index 03b87a96e6ce6..a2025add0b629 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-coupon-meta.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-coupon-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateSalesRuleCoupon" dataType="SalesRuleCoupon" type="create" auth="adminOauth" url="/V1/coupons" method="POST"> <contentType>application/json</contentType> <object key="coupon" dataType="SalesRuleCoupon"> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-label-meta.xml b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-label-meta.xml index 676cb7026c2fa..c462824a47f97 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-label-meta.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-label-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateSalesRuleLabel" dataType="SalesRuleLabel" type="create"> <field key="store_id" required="true">integer</field> <field key="store_label" required="true">string</field> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml index 6e6a203a4e6c5..3b3f7f39a65a0 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateSalesRule" dataType="SalesRule" type="create" auth="adminOauth" url="/V1/salesRules" method="POST"> <contentType>application/json</contentType> <object key="rule" dataType="SalesRule"> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml index 2c260540ae79f..78e10904411c3 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml @@ -6,7 +6,7 @@ */ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminCartPriceRulesPage" url="sales_rule/promo_quote/" area="admin" module="SalesRule"> <section name="AdminCartPriceRulesSection"/> <section name="AdminCartPriceRulesFormSection"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/PriceRuleNewPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/PriceRuleNewPage.xml index 4bedeb88effc8..94967fedf8f01 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Page/PriceRuleNewPage.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Page/PriceRuleNewPage.xml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ --> -<pages xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<pages xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <page name="PriceRuleNewPage" url="sales_rule/promo_quote/new/" area="admin" module="Magento_SalesRule"> <section name="PriceRuleConditionsSection"/> </page> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml index f31ff1a456898..fcacb3a3a37bc 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCartPriceRulesFormSection"> <element name="save" type="button" selector="#save" timeout="30"/> <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml index 5fe5fc1e34687..a32c50d9d8617 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCartPriceRulesSection"> <element name="addNewRuleButton" type="button" selector="#add" timeout="30"/> <element name="messages" type="text" selector=".messages"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/CheckoutCartSummarySection.xml index 5327d53032d48..a5fb96afcc972 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutCartSummarySection"> <element name="discountLabel" type="text" selector="//*[@id='cart-totals']//tr[.//th//span[contains(@class, 'discount coupon')]]"/> <element name="discountTotal" type="text" selector="//*[@id='cart-totals']//tr[.//th//span[contains(@class, 'discount coupon')]]//td//span//span[@class='price']"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml index cc9ab6724528c..cbab097c5291b 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="DiscountSection"> <element name="DiscountTab" type="button" selector="//strong[text()='Apply Discount Code']"/> <element name="CouponInput" type="input" selector="#coupon_code"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml index 1b05ec838dc98..9a74ced2a2c17 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/PriceRuleConditionsSection.xml @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ --> -<sections xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<sections xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <section name="PriceRuleConditionsSection"> <element name="conditionsTab" type="text" selector="//div[@data-index='conditions']//span[contains(.,'Conditions')][1]"/> <element name="createNewRule" type="text" selector="span.rule-param.rule-param-new-child"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml index 39d85ae5fa69b..be52aa05f5af1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontSalesRuleCartCouponSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontSalesRuleCartCouponSection"> <element name="couponHeader" type="button" selector="//*[@id='block-discount-heading']"/> <element name="couponField" type="text" selector="//*[@id='coupon_code']"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateBuyXGetYFreeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateBuyXGetYFreeTest.xml index d21034bc9248f..c69fa97efc034 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateBuyXGetYFreeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateBuyXGetYFreeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateBuyXGetYFreeTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml index a33a4b819b530..88d8f1945ce68 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForCouponCodeTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCartPriceRuleForCouponCodeTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml index cf8879514f5a5..c1aeebfca520e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCartPriceRuleForGeneratedCouponTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountDiscountTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountDiscountTest.xml index adf8b391a68bf..30aa26b26ed39 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountDiscountTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountDiscountTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateFixedAmountDiscountTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountWholeCartDiscountTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountWholeCartDiscountTest.xml index c482d25828397..7ac69f82f79da 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountWholeCartDiscountTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateFixedAmountWholeCartDiscountTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateFixedAmountWholeCartDiscountTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml index 2de7952cd1208..eb04ce04f0718 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreatePercentOfProductPriceTest"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index 05ea5a32574c8..da9eb8e19790e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <before> <createData entity="ApiSalesRule" stepKey="createSalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index c0ef70dbd048a..d735d5a73f0f5 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <before> <createData entity="ApiSalesRule" stepKey="createSalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/PriceRuleCategoryNestingTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/PriceRuleCategoryNestingTest.xml index a46fc19a51cc8..091e09e32f1e6 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/PriceRuleCategoryNestingTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/PriceRuleCategoryNestingTest.xml @@ -4,13 +4,16 @@ * See COPYING.txt for license details. */ --> -<tests xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<tests xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <test name="PriceRuleCategoryNestingTest"> <annotations> + <features value="SalesRule"/> + <stories value="Create categories"/> + <title value="Category nesting level must be the same as were created in categories."/> <description value="Category nesting level must be the same as were created in categories."/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-91101"/> - <group value="sale_rules"/> + <group value="SalesRule"/> </annotations> <before> <createData entity="_defaultCategory" stepKey="subcategory1"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml index 508b16721b2df..ca897bffe8b79 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCartPriceRuleCountry"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml index e025d8b2a3b68..83854c4a767ca 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCartPriceRulePostcode"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml index 3e54620d24937..60a19074317fc 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCartPriceRuleQuantity"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml index 98c4b1144b475..f98f7b408436f 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCartPriceRuleState"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml index 93c64903a337e..6567beba198eb 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontCartPriceRuleSubtotal"> <annotations> <features value="SalesRule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/composer.json b/app/code/Magento/SalesRule/Test/Mftf/composer.json deleted file mode 100644 index 8839051a0848e..0000000000000 --- a/app/code/Magento/SalesRule/Test/Mftf/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-sales-rule-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php b/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php index 8bcffcab9ca0a..2ef77d72a8af5 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php @@ -50,6 +50,9 @@ class GenerateTest extends \PHPUnit\Framework\TestCase /** @var CouponGenerator | \PHPUnit_Framework_MockObject_MockObject */ private $couponGenerator; + /** + * Test setup + */ protected function setUp() { $this->contextMock = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class) @@ -109,6 +112,9 @@ protected function setUp() ); } + /** + * testExecute + */ public function testExecute() { $helperData = $this->getMockBuilder(\Magento\Framework\Json\Helper\Data::class) @@ -143,7 +149,7 @@ public function testExecute() ->with($requestData) ->willReturn(['some_data', 'some_data_2']); $this->messageManager->expects($this->once()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->responseMock->expects($this->once()) ->method('representJson') ->with(); diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/CouponGeneratorTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/CouponGeneratorTest.php new file mode 100644 index 0000000000000..24ea8f2ab5efb --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Model/CouponGeneratorTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Test\Unit\Model; + +use Magento\SalesRule\Api\Data\CouponGenerationSpecInterface; +use Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory; +use Magento\SalesRule\Model\CouponGenerator; +use Magento\SalesRule\Model\Service\CouponManagementService; + +/** + * @covers \Magento\SalesRule\Model\CouponGenerator + */ +class CouponGeneratorTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var CouponGenerator + */ + private $couponGenerator; + + /** + * @var CouponManagementService|\PHPUnit_Framework_MockObject_MockObject + */ + private $couponManagementServiceMock; + + /** + * @var CouponGenerationSpecInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $generationSpecFactoryMock; + + /** + * @var CouponGenerationSpecInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $generationSpecMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->generationSpecFactoryMock = $this->getMockBuilder(CouponGenerationSpecInterfaceFactory::class) + ->disableOriginalConstructor()->setMethods(['create'])->getMock(); + $this->couponManagementServiceMock = $this->createMock(CouponManagementService::class); + $this->generationSpecMock = $this->createMock(CouponGenerationSpecInterface::class); + $this->couponGenerator = new CouponGenerator( + $this->couponManagementServiceMock, + $this->generationSpecFactoryMock + ); + } + + /** + * Test beforeSave method + * + * @return void + */ + public function testBeforeSave() + { + $expected = ['test']; + $this->generationSpecFactoryMock->expects($this->once())->method('create') + ->willReturn($this->generationSpecMock); + $this->couponManagementServiceMock->expects($this->once())->method('generate') + ->with($this->generationSpecMock)->willReturn($expected); + $actual = $this->couponGenerator->generateCodes([]); + self::assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php index 0bce282747b16..51c7b9ede5aa2 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -3,11 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\SalesRule\Test\Unit\Model\Rule\Condition; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\ScopeResolverInterface; use \Magento\Framework\DB\Adapter\AdapterInterface; use \Magento\Framework\DB\Select; -use \Magento\Framework\Model\AbstractModel; +use Magento\Framework\Locale\Format; +use Magento\Framework\Locale\ResolverInterface; use Magento\Quote\Model\Quote\Item\AbstractItem; use \Magento\Rule\Model\Condition\Context; use \Magento\Backend\Helper\Data; @@ -50,8 +54,8 @@ class ProductTest extends \PHPUnit\Framework\TestCase /** @var Collection|\PHPUnit_Framework_MockObject_MockObject */ protected $collectionMock; - /** @var FormatInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $formatMock; + /** @var FormatInterface */ + protected $format; /** @var AttributeLoaderInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $attributeLoaderInterfaceMock; @@ -130,8 +134,12 @@ protected function setUp() $this->collectionMock = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); - $this->formatMock = $this->getMockBuilder(FormatInterface::class) - ->getMockForAbstractClass(); + $this->format = new Format( + $this->getMockBuilder(ScopeResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(ResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(CurrencyFactory::class)->disableOriginalConstructor()->getMock() + ); + $this->model = new SalesRuleProduct( $this->contextMock, $this->backendHelperMock, @@ -140,7 +148,7 @@ protected function setUp() $this->productRepositoryMock, $this->productMock, $this->collectionMock, - $this->formatMock + $this->format ); } @@ -199,7 +207,10 @@ public function testGetValueElementChooserUrl($attribute, $url, $jsObject = '') $this->assertEquals($url, $this->model->getValueElementChooserUrl()); } - public function testValidateCategoriesIgnoresVisibility() + /** + * test ValidateCategoriesIgnoresVisibility + */ + public function testValidateCategoriesIgnoresVisibility(): void { /* @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */ $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) @@ -231,4 +242,81 @@ public function testValidateCategoriesIgnoresVisibility() $this->model->validate($item); } + + /** + * @param boolean $isValid + * @param string $conditionValue + * @param string $operator + * @param double $productPrice + * @dataProvider localisationProvider + */ + public function testQuoteLocaleFormatPrice($isValid, $conditionValue, $operator = '>=', $productPrice = 2000.00) + { + $attr = $this->getMockBuilder(\Magento\Framework\Model\ResourceModel\Db\AbstractDb::class) + ->disableOriginalConstructor() + ->setMethods(['getAttribute']) + ->getMockForAbstractClass(); + + $attr->expects($this->any()) + ->method('getAttribute') + ->willReturn(''); + + /* @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject $product */ + $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['setQuoteItemPrice', 'getResource', 'hasData', 'getData',]) + ->getMock(); + + $product->expects($this->any()) + ->method('setQuoteItemPrice') + ->willReturnSelf(); + + $product->expects($this->any()) + ->method('getResource') + ->willReturn($attr); + + $product->expects($this->any()) + ->method('hasData') + ->willReturn(true); + + $product->expects($this->any()) + ->method('getData') + ->with('quote_item_price') + ->willReturn($productPrice); + + /* @var AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + $item = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['getPrice', 'getProduct',]) + ->getMockForAbstractClass(); + + $item->expects($this->any()) + ->method('getPrice') + ->willReturn($productPrice); + + $item->expects($this->any()) + ->method('getProduct') + ->willReturn($product); + + $this->model->setAttribute('quote_item_price') + ->setOperator($operator); + + $this->assertEquals($isValid, $this->model->setValue($conditionValue)->validate($item)); + } + + /** + * DataProvider for testQuoteLocaleFormatPrice + * + * @return array + */ + public function localisationProvider(): array + { + return [ + 'number' => [true, 500.01], + 'locale' => [true, '1,500.03'], + 'operation' => [true, '1,500.03', '!='], + 'stringOperation' => [false, '1,500.03', '{}'], + 'smallPrice' => [false, '1,500.03', '>=', 1000], + ]; + } } diff --git a/app/code/Magento/SalesRule/etc/db_schema_whitelist.json b/app/code/Magento/SalesRule/etc/db_schema_whitelist.json index e72a0c687331a..f05591b276231 100644 --- a/app/code/Magento/SalesRule/etc/db_schema_whitelist.json +++ b/app/code/Magento/SalesRule/etc/db_schema_whitelist.json @@ -1,230 +1,230 @@ { - "salesrule": { - "column": { - "rule_id": true, - "name": true, - "description": true, - "from_date": true, - "to_date": true, - "uses_per_customer": true, - "is_active": true, - "conditions_serialized": true, - "actions_serialized": true, - "stop_rules_processing": true, - "is_advanced": true, - "product_ids": true, - "sort_order": true, - "simple_action": true, - "discount_amount": true, - "discount_qty": true, - "discount_step": true, - "apply_to_shipping": true, - "times_used": true, - "is_rss": true, - "coupon_type": true, - "use_auto_generation": true, - "uses_per_coupon": true - }, - "index": { - "SALESRULE_IS_ACTIVE_SORT_ORDER_TO_DATE_FROM_DATE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "salesrule_coupon": { - "column": { - "coupon_id": true, - "rule_id": true, - "code": true, - "usage_limit": true, - "usage_per_customer": true, - "times_used": true, - "expiration_date": true, - "is_primary": true, - "created_at": true, - "type": true - }, - "index": { - "SALESRULE_COUPON_RULE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_COUPON_RULE_ID_SALESRULE_RULE_ID": true, - "SALESRULE_COUPON_CODE": true, - "SALESRULE_COUPON_RULE_ID_IS_PRIMARY": true - } - }, - "salesrule_coupon_usage": { - "column": { - "coupon_id": true, - "customer_id": true, - "times_used": true - }, - "index": { - "SALESRULE_COUPON_USAGE_CUSTOMER_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_COUPON_USAGE_COUPON_ID_SALESRULE_COUPON_COUPON_ID": true, - "SALESRULE_COUPON_USAGE_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true - } - }, - "salesrule_customer": { - "column": { - "rule_customer_id": true, - "rule_id": true, - "customer_id": true, - "times_used": true - }, - "index": { - "SALESRULE_CUSTOMER_RULE_ID_CUSTOMER_ID": true, - "SALESRULE_CUSTOMER_CUSTOMER_ID_RULE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_CUSTOMER_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "SALESRULE_CUSTOMER_RULE_ID_SALESRULE_RULE_ID": true - } - }, - "salesrule_label": { - "column": { - "label_id": true, - "rule_id": true, - "store_id": true, - "label": true - }, - "index": { - "SALESRULE_LABEL_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_LABEL_RULE_ID_SALESRULE_RULE_ID": true, - "SALESRULE_LABEL_STORE_ID_STORE_STORE_ID": true, - "SALESRULE_LABEL_RULE_ID_STORE_ID": true - } - }, - "salesrule_product_attribute": { - "column": { - "rule_id": true, - "website_id": true, - "customer_group_id": true, - "attribute_id": true - }, - "index": { - "SALESRULE_PRODUCT_ATTRIBUTE_WEBSITE_ID": true, - "SALESRULE_PRODUCT_ATTRIBUTE_CUSTOMER_GROUP_ID": true, - "SALESRULE_PRODUCT_ATTRIBUTE_ATTRIBUTE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_PRD_ATTR_ATTR_ID_EAV_ATTR_ATTR_ID": true, - "SALESRULE_PRD_ATTR_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, - "SALESRULE_PRODUCT_ATTRIBUTE_RULE_ID_SALESRULE_RULE_ID": true, - "SALESRULE_PRODUCT_ATTRIBUTE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "salesrule_coupon_aggregated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "coupon_code": true, - "coupon_uses": true, - "subtotal_amount": true, - "discount_amount": true, - "total_amount": true, - "subtotal_amount_actual": true, - "discount_amount_actual": true, - "total_amount_actual": true, - "rule_name": true - }, - "index": { - "SALESRULE_COUPON_AGGREGATED_STORE_ID": true, - "SALESRULE_COUPON_AGGREGATED_RULE_NAME": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_COUPON_AGGREGATED_STORE_ID_STORE_STORE_ID": true, - "SALESRULE_COUPON_AGGRED_PERIOD_STORE_ID_ORDER_STS_COUPON_CODE": true - } - }, - "salesrule_coupon_aggregated_updated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "coupon_code": true, - "coupon_uses": true, - "subtotal_amount": true, - "discount_amount": true, - "total_amount": true, - "subtotal_amount_actual": true, - "discount_amount_actual": true, - "total_amount_actual": true, - "rule_name": true - }, - "index": { - "SALESRULE_COUPON_AGGREGATED_UPDATED_STORE_ID": true, - "SALESRULE_COUPON_AGGREGATED_UPDATED_RULE_NAME": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_COUPON_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, - "UNQ_7196FA120A4F0F84E1B66605E87E213E": true - } - }, - "salesrule_coupon_aggregated_order": { - "column": { - "id": true, - "period": true, - "store_id": true, - "order_status": true, - "coupon_code": true, - "coupon_uses": true, - "subtotal_amount": true, - "discount_amount": true, - "total_amount": true, - "rule_name": true - }, - "index": { - "SALESRULE_COUPON_AGGREGATED_ORDER_STORE_ID": true, - "SALESRULE_COUPON_AGGREGATED_ORDER_RULE_NAME": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_COUPON_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, - "UNQ_1094D1FBBCBB11704A29DEF3ACC37D2B": true - } - }, - "salesrule_website": { - "column": { - "rule_id": true, - "website_id": true - }, - "index": { - "SALESRULE_WEBSITE_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_WEBSITE_RULE_ID_SALESRULE_RULE_ID": true, - "SALESRULE_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true - } - }, - "salesrule_customer_group": { - "column": { - "rule_id": true, - "customer_group_id": true - }, - "index": { - "SALESRULE_CUSTOMER_GROUP_CUSTOMER_GROUP_ID": true - }, - "constraint": { - "PRIMARY": true, - "SALESRULE_CUSTOMER_GROUP_RULE_ID_SALESRULE_RULE_ID": true, - "SALESRULE_CSTR_GROUP_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true + "salesrule": { + "column": { + "rule_id": true, + "name": true, + "description": true, + "from_date": true, + "to_date": true, + "uses_per_customer": true, + "is_active": true, + "conditions_serialized": true, + "actions_serialized": true, + "stop_rules_processing": true, + "is_advanced": true, + "product_ids": true, + "sort_order": true, + "simple_action": true, + "discount_amount": true, + "discount_qty": true, + "discount_step": true, + "apply_to_shipping": true, + "times_used": true, + "is_rss": true, + "coupon_type": true, + "use_auto_generation": true, + "uses_per_coupon": true + }, + "index": { + "SALESRULE_IS_ACTIVE_SORT_ORDER_TO_DATE_FROM_DATE": true + }, + "constraint": { + "PRIMARY": true + } + }, + "salesrule_coupon": { + "column": { + "coupon_id": true, + "rule_id": true, + "code": true, + "usage_limit": true, + "usage_per_customer": true, + "times_used": true, + "expiration_date": true, + "is_primary": true, + "created_at": true, + "type": true + }, + "index": { + "SALESRULE_COUPON_RULE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_COUPON_RULE_ID_SALESRULE_RULE_ID": true, + "SALESRULE_COUPON_CODE": true, + "SALESRULE_COUPON_RULE_ID_IS_PRIMARY": true + } + }, + "salesrule_coupon_usage": { + "column": { + "coupon_id": true, + "customer_id": true, + "times_used": true + }, + "index": { + "SALESRULE_COUPON_USAGE_CUSTOMER_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_COUPON_USAGE_COUPON_ID_SALESRULE_COUPON_COUPON_ID": true, + "SALESRULE_COUPON_USAGE_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true + } + }, + "salesrule_customer": { + "column": { + "rule_customer_id": true, + "rule_id": true, + "customer_id": true, + "times_used": true + }, + "index": { + "SALESRULE_CUSTOMER_RULE_ID_CUSTOMER_ID": true, + "SALESRULE_CUSTOMER_CUSTOMER_ID_RULE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_CUSTOMER_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "SALESRULE_CUSTOMER_RULE_ID_SALESRULE_RULE_ID": true + } + }, + "salesrule_label": { + "column": { + "label_id": true, + "rule_id": true, + "store_id": true, + "label": true + }, + "index": { + "SALESRULE_LABEL_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_LABEL_RULE_ID_SALESRULE_RULE_ID": true, + "SALESRULE_LABEL_STORE_ID_STORE_STORE_ID": true, + "SALESRULE_LABEL_RULE_ID_STORE_ID": true + } + }, + "salesrule_product_attribute": { + "column": { + "rule_id": true, + "website_id": true, + "customer_group_id": true, + "attribute_id": true + }, + "index": { + "SALESRULE_PRODUCT_ATTRIBUTE_WEBSITE_ID": true, + "SALESRULE_PRODUCT_ATTRIBUTE_CUSTOMER_GROUP_ID": true, + "SALESRULE_PRODUCT_ATTRIBUTE_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_PRD_ATTR_ATTR_ID_EAV_ATTR_ATTR_ID": true, + "SALESRULE_PRD_ATTR_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true, + "SALESRULE_PRODUCT_ATTRIBUTE_RULE_ID_SALESRULE_RULE_ID": true, + "SALESRULE_PRODUCT_ATTRIBUTE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "salesrule_coupon_aggregated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "coupon_code": true, + "coupon_uses": true, + "subtotal_amount": true, + "discount_amount": true, + "total_amount": true, + "subtotal_amount_actual": true, + "discount_amount_actual": true, + "total_amount_actual": true, + "rule_name": true + }, + "index": { + "SALESRULE_COUPON_AGGREGATED_STORE_ID": true, + "SALESRULE_COUPON_AGGREGATED_RULE_NAME": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_COUPON_AGGREGATED_STORE_ID_STORE_STORE_ID": true, + "SALESRULE_COUPON_AGGRED_PERIOD_STORE_ID_ORDER_STS_COUPON_CODE": true + } + }, + "salesrule_coupon_aggregated_updated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "coupon_code": true, + "coupon_uses": true, + "subtotal_amount": true, + "discount_amount": true, + "total_amount": true, + "subtotal_amount_actual": true, + "discount_amount_actual": true, + "total_amount_actual": true, + "rule_name": true + }, + "index": { + "SALESRULE_COUPON_AGGREGATED_UPDATED_STORE_ID": true, + "SALESRULE_COUPON_AGGREGATED_UPDATED_RULE_NAME": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_COUPON_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, + "UNQ_7196FA120A4F0F84E1B66605E87E213E": true + } + }, + "salesrule_coupon_aggregated_order": { + "column": { + "id": true, + "period": true, + "store_id": true, + "order_status": true, + "coupon_code": true, + "coupon_uses": true, + "subtotal_amount": true, + "discount_amount": true, + "total_amount": true, + "rule_name": true + }, + "index": { + "SALESRULE_COUPON_AGGREGATED_ORDER_STORE_ID": true, + "SALESRULE_COUPON_AGGREGATED_ORDER_RULE_NAME": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_COUPON_AGGREGATED_ORDER_STORE_ID_STORE_STORE_ID": true, + "UNQ_1094D1FBBCBB11704A29DEF3ACC37D2B": true + } + }, + "salesrule_website": { + "column": { + "rule_id": true, + "website_id": true + }, + "index": { + "SALESRULE_WEBSITE_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_WEBSITE_RULE_ID_SALESRULE_RULE_ID": true, + "SALESRULE_WEBSITE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } + }, + "salesrule_customer_group": { + "column": { + "rule_id": true, + "customer_group_id": true + }, + "index": { + "SALESRULE_CUSTOMER_GROUP_CUSTOMER_GROUP_ID": true + }, + "constraint": { + "PRIMARY": true, + "SALESRULE_CUSTOMER_GROUP_RULE_ID_SALESRULE_RULE_ID": true, + "SALESRULE_CSTR_GROUP_CSTR_GROUP_ID_CSTR_GROUP_CSTR_GROUP_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/SalesSequence/Test/Mftf/composer.json b/app/code/Magento/SalesSequence/Test/Mftf/composer.json deleted file mode 100644 index 5649df3b7e0ea..0000000000000 --- a/app/code/Magento/SalesSequence/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-sequence", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesSequence/etc/db_schema_whitelist.json b/app/code/Magento/SalesSequence/etc/db_schema_whitelist.json index 37a5d715ab024..17fec04e24467 100644 --- a/app/code/Magento/SalesSequence/etc/db_schema_whitelist.json +++ b/app/code/Magento/SalesSequence/etc/db_schema_whitelist.json @@ -1,32 +1,32 @@ { - "sales_sequence_profile": { - "column": { - "profile_id": true, - "meta_id": true, - "prefix": true, - "suffix": true, - "start_value": true, - "step": true, - "max_value": true, - "warning_value": true, - "is_active": true + "sales_sequence_profile": { + "column": { + "profile_id": true, + "meta_id": true, + "prefix": true, + "suffix": true, + "start_value": true, + "step": true, + "max_value": true, + "warning_value": true, + "is_active": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SEQUENCE_PROFILE_META_ID_SALES_SEQUENCE_META_META_ID": true, + "SALES_SEQUENCE_PROFILE_META_ID_PREFIX_SUFFIX": true + } }, - "constraint": { - "PRIMARY": true, - "SALES_SEQUENCE_PROFILE_META_ID_SALES_SEQUENCE_META_META_ID": true, - "SALES_SEQUENCE_PROFILE_META_ID_PREFIX_SUFFIX": true + "sales_sequence_meta": { + "column": { + "meta_id": true, + "entity_type": true, + "store_id": true, + "sequence_table": true + }, + "constraint": { + "PRIMARY": true, + "SALES_SEQUENCE_META_ENTITY_TYPE_STORE_ID": true + } } - }, - "sales_sequence_meta": { - "column": { - "meta_id": true, - "entity_type": true, - "store_id": true, - "sequence_table": true - }, - "constraint": { - "PRIMARY": true, - "SALES_SEQUENCE_META_ENTITY_TYPE_STORE_ID": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/SampleData/Test/Mftf/composer.json b/app/code/Magento/SampleData/Test/Mftf/composer.json deleted file mode 100644 index 039b87fa858b5..0000000000000 --- a/app/code/Magento/SampleData/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-sample-data", - "description": "Sample Data fixtures", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/sample-data-media": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php b/app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php index 03c298b23a43e..5ead8437f943a 100644 --- a/app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php +++ b/app/code/Magento/Search/Block/Adminhtml/Dashboard/Last.php @@ -63,6 +63,7 @@ protected function _construct() /** * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _prepareCollection() { @@ -86,6 +87,7 @@ protected function _prepareCollection() /** * @return $this + * @throws \Exception */ protected function _prepareColumns() { @@ -118,7 +120,7 @@ protected function _prepareColumns() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRowUrl($row) { diff --git a/app/code/Magento/Search/Block/Adminhtml/Term/Edit/Form.php b/app/code/Magento/Search/Block/Adminhtml/Term/Edit/Form.php index 9019a127c5b64..18d6d2cf54c3a 100644 --- a/app/code/Magento/Search/Block/Adminhtml/Term/Edit/Form.php +++ b/app/code/Magento/Search/Block/Adminhtml/Term/Edit/Form.php @@ -51,6 +51,8 @@ protected function _construct() * Prepare form fields * * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function _prepareForm() diff --git a/app/code/Magento/Search/Block/Term.php b/app/code/Magento/Search/Block/Term.php index e6c5c2b82ab5e..b27ef6b01fda2 100644 --- a/app/code/Magento/Search/Block/Term.php +++ b/app/code/Magento/Search/Block/Term.php @@ -71,6 +71,7 @@ public function __construct( * Load terms and try to sort it by names * * @return $this + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _loadTerms() { @@ -109,6 +110,7 @@ protected function _loadTerms() /** * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getTerms() { diff --git a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Delete.php b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Delete.php index bb476423692d1..9d8b612cefadf 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Delete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Delete.php @@ -60,16 +60,18 @@ public function execute() /** @var \Magento\Search\Model\SynonymGroup $synGroupModel */ $synGroupModel = $this->synGroupRepository->get($id); $this->synGroupRepository->delete($synGroupModel); - $this->messageManager->addSuccess(__('The synonym group has been deleted.')); + $this->messageManager->addSuccessMessage(__('The synonym group has been deleted.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->logger->error($e); } catch (\Exception $e) { - $this->messageManager->addError(__('An error was encountered while performing delete operation.')); + $this->messageManager->addErrorMessage( + __('An error was encountered while performing delete operation.') + ); $this->logger->error($e); } } else { - $this->messageManager->addError(__('We can\'t find a synonym group to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a synonym group to delete.')); } return $resultRedirect->setPath('*/*/'); diff --git a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Edit.php b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Edit.php index 506976247327f..adc4ecc52030a 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Edit.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Edit.php @@ -34,7 +34,6 @@ class Edit extends \Magento\Backend\App\Action * Edit constructor. * * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Backend\Model\Session $session * @param \Magento\Framework\Registry $registry * @param \Magento\Search\Controller\Adminhtml\Synonyms\ResultPageBuilder $pageBuilder * @param \Magento\Search\Api\SynonymGroupRepositoryInterface $synGroupRepository @@ -66,7 +65,7 @@ public function execute() // 2. Initial checking if ($groupId && (!$synGroup->getGroupId())) { - $this->messageManager->addError(__('This synonyms group no longer exists.')); + $this->messageManager->addErrorMessage(__('This synonyms group no longer exists.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); return $resultRedirect->setPath('*/*/'); diff --git a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/MassDelete.php b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/MassDelete.php index 4add418d95325..f2770f77cc533 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/MassDelete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/MassDelete.php @@ -72,17 +72,17 @@ public function execute() $this->synGroupRepository->delete($synonymGroup); $deletedItems++; } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } if ($deletedItems != 0) { if ($collectionSize != $deletedItems) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Failed to delete %1 synonym group(s).', $collectionSize - $deletedItems) ); } - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('A total of %1 synonym group(s) have been deleted.', $deletedItems) ); } diff --git a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Save.php b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Save.php index ffa97ceb3e0e1..0ed73fd0cee32 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Save.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Synonyms/Save.php @@ -59,7 +59,7 @@ public function execute() $synGroup = $this->synGroupRepository->get($synGroupId); if (!$synGroup->getGroupId() && $synGroupId) { - $this->messageManager->addError(__('This synonym group no longer exists.')); + $this->messageManager->addErrorMessage(__('This synonym group no longer exists.')); return $resultRedirect->setPath('*/*/'); } diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php index 3a1b80df2ea7e..c7adf32da0fb0 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Delete.php @@ -23,16 +23,16 @@ public function execute() $model = $this->_objectManager->create(\Magento\Search\Model\Query::class); $model->setId($id); $model->delete(); - $this->messageManager->addSuccess(__('You deleted the search.')); + $this->messageManager->addSuccessMessage(__('You deleted the search.')); $resultRedirect->setPath('search/*/'); return $resultRedirect; } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $resultRedirect->setPath('search/*/edit', ['id' => $this->getRequest()->getParam('id')]); return $resultRedirect; } } - $this->messageManager->addError(__('We can\'t find a search term to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a search term to delete.')); $resultRedirect->setPath('search/*/'); return $resultRedirect; } diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php index 85e14ae9fe0b0..3ee0ea240377f 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Edit.php @@ -43,7 +43,7 @@ public function execute() if ($id) { $model->load($id); if (!$model->getId()) { - $this->messageManager->addError(__('This search no longer exists.')); + $this->messageManager->addErrorMessage(__('This search no longer exists.')); /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $resultRedirect->setPath('search/*'); diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php index 75173381b5239..128fa1c6c687c 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchCsv.php @@ -34,6 +34,7 @@ public function __construct( * Export search report grid to CSV format * * @return \Magento\Framework\App\ResponseInterface + * @throws \Exception */ public function execute() { diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchExcel.php b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchExcel.php index e3859bc5aefd2..d7e2b02bc5249 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchExcel.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/ExportSearchExcel.php @@ -34,6 +34,7 @@ public function __construct( * Export search report to Excel XML format * * @return \Magento\Framework\App\ResponseInterface + * @throws \Exception */ public function execute() { diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php b/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php index b38d883b8faae..f6874078f2f64 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/MassDelete.php @@ -17,16 +17,16 @@ public function execute() { $searchIds = $this->getRequest()->getParam('search'); if (!is_array($searchIds)) { - $this->messageManager->addError(__('Please select searches.')); + $this->messageManager->addErrorMessage(__('Please select searches.')); } else { try { foreach ($searchIds as $searchId) { $model = $this->_objectManager->create(\Magento\Search\Model\Query::class)->load($searchId); $model->delete(); } - $this->messageManager->addSuccess(__('Total of %1 record(s) were deleted.', count($searchIds))); + $this->messageManager->addSuccessMessage(__('Total of %1 record(s) were deleted.', count($searchIds))); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php b/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php index 42e9373a20fe2..cd9b1347ed1ed 100644 --- a/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php +++ b/app/code/Magento/Search/Controller/Adminhtml/Term/Save.php @@ -45,12 +45,15 @@ public function execute() $model->addData($data); $model->setIsProcessed(0); $model->save(); - $this->messageManager->addSuccess(__('You saved the search term.')); + $this->messageManager->addSuccessMessage(__('You saved the search term.')); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); return $this->proceedToEdit($data); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Something went wrong while saving the search query.')); + $this->messageManager->addExceptionMessage( + $e, + __('Something went wrong while saving the search query.') + ); return $this->proceedToEdit($data); } } diff --git a/app/code/Magento/Search/Controller/Term/Popular.php b/app/code/Magento/Search/Controller/Term/Popular.php index acc1ce324567e..0573fe6a81a12 100644 --- a/app/code/Magento/Search/Controller/Term/Popular.php +++ b/app/code/Magento/Search/Controller/Term/Popular.php @@ -34,6 +34,7 @@ public function __construct(Context $context, ScopeConfigInterface $scopeConfig) * * @param \Magento\Framework\App\RequestInterface $request * @return \Magento\Framework\App\ResponseInterface + * @throws \Magento\Framework\Exception\NotFoundException */ public function dispatch(RequestInterface $request) { diff --git a/app/code/Magento/Search/Model/Query.php b/app/code/Magento/Search/Model/Query.php index 404d93f76da6d..365e03b3648a7 100644 --- a/app/code/Magento/Search/Model/Query.php +++ b/app/code/Magento/Search/Model/Query.php @@ -147,6 +147,7 @@ public function getSearchCollection() * Retrieve collection of suggest queries * * @return QueryCollection + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getSuggestCollection() { @@ -167,6 +168,7 @@ public function getSuggestCollection() * * @param string $text * @return $this + * @throws \Magento\Framework\Exception\LocalizedException * @deprecated 100.1.0 "synonym for" feature has been removed */ public function loadByQuery($text) @@ -180,6 +182,7 @@ public function loadByQuery($text) * * @param string $text * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function loadByQueryText($text) { @@ -204,6 +207,7 @@ public function setStoreId($storeId) * Retrieve store Id * * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getStoreId() { @@ -217,6 +221,7 @@ public function getStoreId() * Prepare save query for result * * @return $this + * @throws \Exception */ public function prepare() { @@ -264,6 +269,7 @@ public function saveNumResults($numResults) * Retrieve minimum query length * * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getMinQueryLength() { @@ -278,6 +284,7 @@ public function getMinQueryLength() * Retrieve maximum query length * * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getMaxQueryLength() { diff --git a/app/code/Magento/Search/Model/SynonymGroupRepository.php b/app/code/Magento/Search/Model/SynonymGroupRepository.php index 6c5bdfb597487..75d7049afd949 100644 --- a/app/code/Magento/Search/Model/SynonymGroupRepository.php +++ b/app/code/Magento/Search/Model/SynonymGroupRepository.php @@ -45,7 +45,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(SynonymGroupInterface $synonymGroup, $errorOnMergeConflict = false) { @@ -106,6 +106,7 @@ public function get($synonymGroupId) * @param bool $errorOnMergeConflict * @return SynonymGroupInterface * @throws Synonym\MergeConflictException + * @throws \Magento\Framework\Exception\AlreadyExistsException */ private function create(SynonymGroupInterface $synonymGroup, $errorOnMergeConflict) { @@ -144,6 +145,7 @@ private function create(SynonymGroupInterface $synonymGroup, $errorOnMergeConfli * @param SynonymGroupInterface $synonymGroupToMerge * @param array $matchingGroupIds * @return array + * @throws \Exception */ private function merge(SynonymGroupInterface $synonymGroupToMerge, array $matchingGroupIds) { @@ -182,6 +184,7 @@ private function populateSynonymGroupModel(SynonymGroup $modelToPopulate, Synony * @param bool $errorOnMergeConflict * @return SynonymGroupInterface * @throws Synonym\MergeConflictException + * @throws \Magento\Framework\Exception\AlreadyExistsException */ private function update( SynonymGroup $oldSynonymGroup, diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml index 543725fc5fa1c..2b08e9b4b85ec 100644 --- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml +++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontQuickSearchSection"> <element name="searchPhrase" type="input" selector="#search"/> <element name="searchButton" type="button" selector="button.action.search" timeout="30"/> diff --git a/app/code/Magento/Search/Test/Mftf/composer.json b/app/code/Magento/Search/Test/Mftf/composer.json deleted file mode 100644 index 97cef910ffa02..0000000000000 --- a/app/code/Magento/Search/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-search", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-search": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Synonyms/DeleteTest.php b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Synonyms/DeleteTest.php index a7f71941dc6b2..38c78b986faf4 100644 --- a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Synonyms/DeleteTest.php +++ b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Synonyms/DeleteTest.php @@ -107,10 +107,10 @@ public function testDeleteAction() $this->repository->expects($this->once())->method('get')->with(10)->willReturn($this->synonymGroupMock); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('The synonym group has been deleted.')); - $this->messageManagerMock->expects($this->never())->method('addError'); + $this->messageManagerMock->expects($this->never())->method('addErrorMessage'); $this->resultRedirectMock->expects($this->once())->method('setPath')->with('*/*/')->willReturnSelf(); @@ -124,10 +124,10 @@ public function testDeleteActionNoId() ->willReturn(null); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('We can\'t find a synonym group to delete.')); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/MassDeleteTest.php b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/MassDeleteTest.php index efda8f52fcfe9..60cc958a6187c 100644 --- a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/MassDeleteTest.php +++ b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/MassDeleteTest.php @@ -54,7 +54,7 @@ protected function setUp() ->getMockForAbstractClass(); $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) ->disableOriginalConstructor() - ->setMethods(['addSuccess', 'addError']) + ->setMethods(['addSuccessMessage', 'addErrorMessage']) ->getMockForAbstractClass(); $this->pageFactory = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) ->setMethods([]) @@ -107,7 +107,7 @@ public function testExecute() $this->createQuery(0, 1); $this->createQuery(1, 2); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->will($this->returnSelf()); $this->resultRedirectMock->expects($this->once()) ->method('setPath') diff --git a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/SaveTest.php b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/SaveTest.php index 09ae2c38fe525..28f4b65cd412f 100644 --- a/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/SaveTest.php +++ b/app/code/Magento/Search/Test/Unit/Controller/Adminhtml/Term/SaveTest.php @@ -75,7 +75,7 @@ protected function setUp() $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) ->disableOriginalConstructor() - ->setMethods(['addSuccess', 'addError', 'addException']) + ->setMethods(['addSuccessMessage', 'addErrorMessage', 'addExceptionMessage']) ->getMockForAbstractClass(); $this->context->expects($this->any()) ->method('getMessageManager') @@ -143,7 +143,7 @@ public function testExecuteLoadQueryQueryId() $this->query->expects($this->once())->method('getId')->willReturn(false); $this->query->expects($this->once())->method('load')->with($queryId); - $this->messageManager->expects($this->once())->method('addSuccess'); + $this->messageManager->expects($this->once())->method('addSuccessMessage'); $this->redirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->assertSame($this->redirect, $this->controller->execute()); @@ -161,7 +161,7 @@ public function testExecuteLoadQueryQueryIdQueryText() $this->query->expects($this->once())->method('loadByQueryText')->with($queryText); $this->query->expects($this->any())->method('getId')->willReturn($queryId); - $this->messageManager->expects($this->once())->method('addSuccess'); + $this->messageManager->expects($this->once())->method('addSuccessMessage'); $this->redirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->assertSame($this->redirect, $this->controller->execute()); @@ -180,7 +180,7 @@ public function testExecuteLoadQueryQueryIdQueryText2() $this->query->expects($this->any())->method('getId')->willReturn(false); $this->query->expects($this->once())->method('load')->with($queryId); - $this->messageManager->expects($this->once())->method('addSuccess'); + $this->messageManager->expects($this->once())->method('addSuccessMessage'); $this->redirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->assertSame($this->redirect, $this->controller->execute()); @@ -199,7 +199,7 @@ public function testExecuteLoadQueryQueryIdQueryTextException() $this->query->expects($this->once())->method('loadByQueryText')->with($queryText); $this->query->expects($this->any())->method('getId')->willReturn($anotherQueryId); - $this->messageManager->expects($this->once())->method('addError'); + $this->messageManager->expects($this->once())->method('addErrorMessage'); $this->session->expects($this->once())->method('setPageData'); $this->redirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->assertSame($this->redirect, $this->controller->execute()); @@ -216,7 +216,7 @@ public function testExecuteException() $this->query->expects($this->once())->method('setStoreId'); $this->query->expects($this->once())->method('loadByQueryText')->willThrowException(new \Exception()); - $this->messageManager->expects($this->once())->method('addException'); + $this->messageManager->expects($this->once())->method('addExceptionMessage'); $this->session->expects($this->once())->method('setPageData'); $this->redirect->expects($this->once())->method('setPath')->willReturnSelf(); $this->assertSame($this->redirect, $this->controller->execute()); diff --git a/app/code/Magento/Search/Ui/Component/Listing/Column/StoreView.php b/app/code/Magento/Search/Ui/Component/Listing/Column/StoreView.php index 1a66d9242b343..405042f2e7825 100644 --- a/app/code/Magento/Search/Ui/Component/Listing/Column/StoreView.php +++ b/app/code/Magento/Search/Ui/Component/Listing/Column/StoreView.php @@ -46,6 +46,7 @@ public function __construct( * * @param array $dataSource * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function prepareDataSource(array $dataSource) { @@ -62,6 +63,7 @@ public function prepareDataSource(array $dataSource) * * @param array $item * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function prepareItem(array $item) { diff --git a/app/code/Magento/Search/Ui/Component/Listing/Column/Website.php b/app/code/Magento/Search/Ui/Component/Listing/Column/Website.php index 216e0b3877ddb..78fd4b9c36a80 100644 --- a/app/code/Magento/Search/Ui/Component/Listing/Column/Website.php +++ b/app/code/Magento/Search/Ui/Component/Listing/Column/Website.php @@ -47,6 +47,7 @@ public function __construct( * * @param array $dataSource * @return array + * @throws \Magento\Framework\Exception\LocalizedException */ public function prepareDataSource(array $dataSource) { @@ -63,6 +64,7 @@ public function prepareDataSource(array $dataSource) * * @param array $item * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ protected function prepareItem(array $item) { diff --git a/app/code/Magento/Search/etc/db_schema_whitelist.json b/app/code/Magento/Search/etc/db_schema_whitelist.json index 72b417b976862..71adbc68887d0 100644 --- a/app/code/Magento/Search/etc/db_schema_whitelist.json +++ b/app/code/Magento/Search/etc/db_schema_whitelist.json @@ -1,46 +1,46 @@ { - "search_query": { - "column": { - "query_id": true, - "query_text": true, - "num_results": true, - "popularity": true, - "redirect": true, - "synonym_for": true, - "store_id": true, - "display_in_terms": true, - "is_active": true, - "is_processed": true, - "updated_at": true + "search_query": { + "column": { + "query_id": true, + "query_text": true, + "num_results": true, + "popularity": true, + "redirect": true, + "synonym_for": true, + "store_id": true, + "display_in_terms": true, + "is_active": true, + "is_processed": true, + "updated_at": true + }, + "index": { + "SEARCH_QUERY_QUERY_TEXT_STORE_ID_POPULARITY": true, + "SEARCH_QUERY_STORE_ID": true, + "SEARCH_QUERY_IS_PROCESSED": true, + "SEARCH_QUERY_SYNONYM_FOR": true + }, + "constraint": { + "PRIMARY": true, + "SEARCH_QUERY_STORE_ID_STORE_STORE_ID": true, + "SEARCH_QUERY_QUERY_TEXT_STORE_ID": true + } }, - "index": { - "SEARCH_QUERY_QUERY_TEXT_STORE_ID_POPULARITY": true, - "SEARCH_QUERY_STORE_ID": true, - "SEARCH_QUERY_IS_PROCESSED": true, - "SEARCH_QUERY_SYNONYM_FOR": true - }, - "constraint": { - "PRIMARY": true, - "SEARCH_QUERY_STORE_ID_STORE_STORE_ID": true, - "SEARCH_QUERY_QUERY_TEXT_STORE_ID": true - } - }, - "search_synonyms": { - "column": { - "group_id": true, - "synonyms": true, - "store_id": true, - "website_id": true - }, - "index": { - "SEARCH_SYNONYMS_SYNONYMS": true, - "SEARCH_SYNONYMS_STORE_ID": true, - "SEARCH_SYNONYMS_WEBSITE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SEARCH_SYNONYMS_STORE_ID_STORE_STORE_ID": true, - "SEARCH_SYNONYMS_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + "search_synonyms": { + "column": { + "group_id": true, + "synonyms": true, + "store_id": true, + "website_id": true + }, + "index": { + "SEARCH_SYNONYMS_SYNONYMS": true, + "SEARCH_SYNONYMS_STORE_ID": true, + "SEARCH_SYNONYMS_WEBSITE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SEARCH_SYNONYMS_STORE_ID_STORE_STORE_ID": true, + "SEARCH_SYNONYMS_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Search/i18n/pt_BR.csv b/app/code/Magento/Search/i18n/pt_BR.csv index 8b4b04aa3b9ec..c10566a7c9800 100644 --- a/app/code/Magento/Search/i18n/pt_BR.csv +++ b/app/code/Magento/Search/i18n/pt_BR.csv @@ -1 +1 @@ -"Search Engine","Search Engine" +"Search Engine","Mecanismo de Busca" diff --git a/app/code/Magento/Security/Controller/Adminhtml/Session/LogoutAll.php b/app/code/Magento/Security/Controller/Adminhtml/Session/LogoutAll.php index c533e740b2251..35d8f22d84d51 100644 --- a/app/code/Magento/Security/Controller/Adminhtml/Session/LogoutAll.php +++ b/app/code/Magento/Security/Controller/Adminhtml/Session/LogoutAll.php @@ -38,11 +38,11 @@ public function execute() { try { $this->sessionsManager->logoutOtherUserSessions(); - $this->messageManager->addSuccess(__('All other open sessions for this account were terminated.')); + $this->messageManager->addSuccessMessage(__('All other open sessions for this account were terminated.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __("We couldn't logout because of an error.")); + $this->messageManager->addExceptionMessage($e, __("We couldn't logout because of an error.")); } $this->_redirect('*/*/activity'); } diff --git a/app/code/Magento/Security/Model/Plugin/AuthSession.php b/app/code/Magento/Security/Model/Plugin/AuthSession.php index abec4dc7c29ef..01203caaa31cd 100644 --- a/app/code/Magento/Security/Model/Plugin/AuthSession.php +++ b/app/code/Magento/Security/Model/Plugin/AuthSession.php @@ -82,7 +82,7 @@ private function addUserLogoutNotification() $this->sessionsManager->getCurrentSession()->getStatus() ); } elseif ($message = $this->sessionsManager->getLogoutReasonMessage()) { - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } return $this; diff --git a/app/code/Magento/Security/Model/Plugin/LoginController.php b/app/code/Magento/Security/Model/Plugin/LoginController.php index ab9c6e2857bce..ba1a18c4f0c06 100644 --- a/app/code/Magento/Security/Model/Plugin/LoginController.php +++ b/app/code/Magento/Security/Model/Plugin/LoginController.php @@ -53,7 +53,7 @@ public function beforeExecute(Login $login) { $logoutReasonCode = $this->securityCookie->getLogoutReasonCookie(); if ($this->isLoginForm($login) && $logoutReasonCode >= 0) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( $this->sessionsManager->getLogoutReasonMessageByStatus($logoutReasonCode) ); $this->securityCookie->deleteLogoutReasonCookie(); diff --git a/app/code/Magento/Security/Test/Mftf/composer.json b/app/code/Magento/Security/Test/Mftf/composer.json deleted file mode 100644 index 3429571c9b6d8..0000000000000 --- a/app/code/Magento/Security/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-security", - "description": "Security management module", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/LogoutAllTest.php b/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/LogoutAllTest.php index 8c2119bde667e..02335ef55aa93 100644 --- a/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/LogoutAllTest.php +++ b/app/code/Magento/Security/Test/Unit/Controller/Adminhtml/Session/LogoutAllTest.php @@ -74,7 +74,7 @@ public function setUp() $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) ->disableOriginalConstructor() - ->setMethods(['addSuccess', 'addError', 'addException']) + ->setMethods(['addSuccessMessage', 'addErrorMessage', 'addExceptionMessage']) ->getMockForAbstractClass(); $this->contextMock->expects($this->any()) ->method('getMessageManager') @@ -132,12 +132,12 @@ public function testExecute() $this->sessionsManager->expects($this->once()) ->method('logoutOtherUserSessions'); $this->messageManager->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with($successMessage); $this->messageManager->expects($this->never()) - ->method('addError'); + ->method('addErrorMessage'); $this->messageManager->expects($this->never()) - ->method('addException'); + ->method('addExceptionMessage'); $this->responseMock->expects($this->once()) ->method('setRedirect'); $this->actionFlagMock->expects($this->once()) @@ -158,7 +158,7 @@ public function testExecuteLocalizedException() ->method('logoutOtherUserSessions') ->willThrowException(new LocalizedException($phrase)); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($phrase); $this->controller->execute(); } @@ -173,7 +173,7 @@ public function testExecuteException() ->method('logoutOtherUserSessions') ->willThrowException(new \Exception()); $this->messageManager->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with(new \Exception(), $phrase); $this->controller->execute(); } diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php index 5cb06d6143023..0f7f590b71de4 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/AuthSessionTest.php @@ -112,7 +112,7 @@ public function testAroundProlongSessionIsNotActiveAndIsNotAjaxRequest() ->willReturn($errorMessage); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($errorMessage); $this->model->aroundProlong($this->authSessionMock, $proceed); diff --git a/app/code/Magento/Security/Test/Unit/Model/Plugin/LoginControllerTest.php b/app/code/Magento/Security/Test/Unit/Model/Plugin/LoginControllerTest.php index 2bb2bc3cafac7..aa066e23f67cb 100644 --- a/app/code/Magento/Security/Test/Unit/Model/Plugin/LoginControllerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/Plugin/LoginControllerTest.php @@ -103,7 +103,7 @@ public function testBeforeExecute() ->willReturn($errorMessage); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($errorMessage); $this->securityCookieMock->expects($this->once()) diff --git a/app/code/Magento/Security/etc/db_schema_whitelist.json b/app/code/Magento/Security/etc/db_schema_whitelist.json index c9115aed6fb82..c387b7591c7a5 100644 --- a/app/code/Magento/Security/etc/db_schema_whitelist.json +++ b/app/code/Magento/Security/etc/db_schema_whitelist.json @@ -1,37 +1,37 @@ { - "admin_user_session": { - "column": { - "id": true, - "session_id": true, - "user_id": true, - "status": true, - "created_at": true, - "updated_at": true, - "ip": true + "admin_user_session": { + "column": { + "id": true, + "session_id": true, + "user_id": true, + "status": true, + "created_at": true, + "updated_at": true, + "ip": true + }, + "index": { + "ADMIN_USER_SESSION_SESSION_ID": true, + "ADMIN_USER_SESSION_USER_ID": true + }, + "constraint": { + "PRIMARY": true, + "ADMIN_USER_SESSION_USER_ID_ADMIN_USER_USER_ID": true + } }, - "index": { - "ADMIN_USER_SESSION_SESSION_ID": true, - "ADMIN_USER_SESSION_USER_ID": true - }, - "constraint": { - "PRIMARY": true, - "ADMIN_USER_SESSION_USER_ID_ADMIN_USER_USER_ID": true - } - }, - "password_reset_request_event": { - "column": { - "id": true, - "request_type": true, - "account_reference": true, - "created_at": true, - "ip": true - }, - "index": { - "PASSWORD_RESET_REQUEST_EVENT_ACCOUNT_REFERENCE": true, - "PASSWORD_RESET_REQUEST_EVENT_CREATED_AT": true - }, - "constraint": { - "PRIMARY": true + "password_reset_request_event": { + "column": { + "id": true, + "request_type": true, + "account_reference": true, + "created_at": true, + "ip": true + }, + "index": { + "PASSWORD_RESET_REQUEST_EVENT_ACCOUNT_REFERENCE": true, + "PASSWORD_RESET_REQUEST_EVENT_CREATED_AT": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/SendFriend/Test/Mftf/composer.json b/app/code/Magento/SendFriend/Test/Mftf/composer.json deleted file mode 100644 index 7e061e0159f02..0000000000000 --- a/app/code/Magento/SendFriend/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-send-friend", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SendFriend/etc/db_schema_whitelist.json b/app/code/Magento/SendFriend/etc/db_schema_whitelist.json index eebb79747432c..5cab97e6dfb1c 100644 --- a/app/code/Magento/SendFriend/etc/db_schema_whitelist.json +++ b/app/code/Magento/SendFriend/etc/db_schema_whitelist.json @@ -1,17 +1,17 @@ { - "sendfriend_log": { - "column": { - "log_id": true, - "ip": true, - "time": true, - "website_id": true - }, - "index": { - "SENDFRIEND_LOG_IP": true, - "SENDFRIEND_LOG_TIME": true - }, - "constraint": { - "PRIMARY": true + "sendfriend_log": { + "column": { + "log_id": true, + "ip": true, + "time": true, + "website_id": true + }, + "index": { + "SENDFRIEND_LOG_IP": true, + "SENDFRIEND_LOG_TIME": true + }, + "constraint": { + "PRIMARY": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging/Grid.php b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging/Grid.php index 9e340cc31ff17..1d3f6ad1ee5a3 100644 --- a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging/Grid.php +++ b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging/Grid.php @@ -10,7 +10,7 @@ class Grid extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'order/packaging/grid.phtml'; + protected $_template = 'Magento_Shipping::order/packaging/grid.phtml'; /** * Core registry diff --git a/app/code/Magento/Shipping/Block/Adminhtml/Order/Tracking/View.php b/app/code/Magento/Shipping/Block/Adminhtml/Order/Tracking/View.php index 00fc6fce1bfb8..356483c9a5dd7 100644 --- a/app/code/Magento/Shipping/Block/Adminhtml/Order/Tracking/View.php +++ b/app/code/Magento/Shipping/Block/Adminhtml/Order/Tracking/View.php @@ -92,11 +92,6 @@ public function getRemoveUrl($track) public function getCarrierTitle($code) { $carrier = $this->_carrierFactory->create($code); - if ($carrier) { - return $carrier->getConfigData('title'); - } else { - return __('Custom Value'); - } - return false; + return $carrier ? $carrier->getConfigData('title') : __('Custom Value'); } } diff --git a/app/code/Magento/Shipping/Block/Order/Shipment.php b/app/code/Magento/Shipping/Block/Order/Shipment.php index 653fb357f0b1d..21e960985d6b6 100644 --- a/app/code/Magento/Shipping/Block/Order/Shipment.php +++ b/app/code/Magento/Shipping/Block/Order/Shipment.php @@ -18,7 +18,7 @@ class Shipment extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'order/shipment.phtml'; + protected $_template = 'Magento_Shipping::order/shipment.phtml'; /** * Core registry diff --git a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php index 5575792c346d3..f9030ee75630b 100644 --- a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php +++ b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrier.php @@ -329,11 +329,24 @@ public function checkAvailableShipCountries(\Magento\Framework\DataObject $reque * @return $this|bool|\Magento\Framework\DataObject * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function proccessAdditionalValidation(\Magento\Framework\DataObject $request) + public function processAdditionalValidation(\Magento\Framework\DataObject $request) { return $this; } + /** + * Processing additional validation to check is carrier applicable. + * + * @param \Magento\Framework\DataObject $request + * @return $this|bool|\Magento\Framework\DataObject + * @deprecated + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function proccessAdditionalValidation(\Magento\Framework\DataObject $request) + { + return $this->processAdditionalValidation($request); + } + /** * Determine whether current carrier enabled for activity * @@ -394,6 +407,9 @@ public function getSortOrder() */ protected function _updateFreeMethodQuote($request) { + if (!$request->getFreeShipping()) { + return; + } if ($request->getFreeMethodWeight() == $request->getPackageWeight() || !$request->hasFreeMethodWeight()) { return; } @@ -435,6 +451,12 @@ protected function _updateFreeMethodQuote($request) } } } + } else { + /** + * if we can apply free shipping for all order we should force price + * to $0.00 for shipping with out sending second request to carrier + */ + $price = 0; } /** diff --git a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php index 8244fcc4bad9d..be2588dc48711 100644 --- a/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php +++ b/app/code/Magento/Shipping/Model/Carrier/AbstractCarrierOnline.php @@ -302,10 +302,24 @@ public function getAllItems(RateRequest $request) * * @param \Magento\Framework\DataObject $request * @return $this|bool|\Magento\Framework\DataObject + * @deprecated * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function proccessAdditionalValidation(\Magento\Framework\DataObject $request) + { + return $this->processAdditionalValidation($request); + } + + /** + * Processing additional validation to check if carrier applicable. + * + * @param \Magento\Framework\DataObject $request + * @return $this|bool|\Magento\Framework\DataObject + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function processAdditionalValidation(\Magento\Framework\DataObject $request) { //Skip by item validation if there is no items in request if (!count($this->getAllItems($request))) { diff --git a/app/code/Magento/Shipping/Model/Shipping.php b/app/code/Magento/Shipping/Model/Shipping.php index 2223cb8ae3bf2..57e055e83a58a 100644 --- a/app/code/Magento/Shipping/Model/Shipping.php +++ b/app/code/Magento/Shipping/Model/Shipping.php @@ -259,7 +259,7 @@ public function collectCarrierRates($carrierCode, $request) $carrier->setActiveFlag($this->_availabilityConfigField); $result = $carrier->checkAvailableShipCountries($request); if (false !== $result && !$result instanceof \Magento\Quote\Model\Quote\Address\RateResult\Error) { - $result = $carrier->proccessAdditionalValidation($request); + $result = $carrier->processAdditionalValidation($request); } /* * Result will be false if the admin set not to show the shipping module diff --git a/app/code/Magento/Shipping/Model/Shipping/Labels.php b/app/code/Magento/Shipping/Model/Shipping/Labels.php index 5c796d9fa6897..08ce168567182 100644 --- a/app/code/Magento/Shipping/Model/Shipping/Labels.php +++ b/app/code/Magento/Shipping/Model/Shipping/Labels.php @@ -117,8 +117,8 @@ public function requestToShipment(Shipment $orderShipment) ) ); - if (!$admin->getFirstname() - || !$admin->getLastname() + if (!$admin->getFirstName() + || !$admin->getLastName() || !$storeInfo->getName() || !$storeInfo->getPhone() || !$originStreet1 @@ -188,8 +188,8 @@ protected function setShipperDetails( ); $request->setShipperContactPersonName($storeAdmin->getName()); - $request->setShipperContactPersonFirstName($storeAdmin->getFirstname()); - $request->setShipperContactPersonLastName($storeAdmin->getLastname()); + $request->setShipperContactPersonFirstName($storeAdmin->getFirstName()); + $request->setShipperContactPersonLastName($storeAdmin->getLastName()); $request->setShipperContactCompanyName($store->getName()); $request->setShipperContactPhoneNumber($store->getPhone()); $request->setShipperEmail($storeAdmin->getEmail()); diff --git a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml index 80db1fe9469a3..85430aeaa4168 100644 --- a/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml +++ b/app/code/Magento/Shipping/Test/Mftf/ActionGroup/AdminShipmentActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="verifyBasicShipmentInformation"> <arguments> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml index 120517bffde8f..6c7e5cf1b18e0 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- Enable Flat Rate Shipping method config --> <entity name="FlatRateShippingMethodConfig" type="flat_rate_shipping_method"> <requiredEntity type="active">flatRateActiveEnable</requiredEntity> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml index fba1970dba294..110795533468e 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <!-- Enable Free Shipping method --> <entity name="FreeShippinMethodConfig" type="free_shipping_method"> <requiredEntity type="active">freeActiveEnable</requiredEntity> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/ShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/ShippingMethodData.xml index 3e8613ec2e43f..1151e55c4378f 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/ShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/ShippingMethodData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="DefaultFlatRateMethod" type="shipping"> <data key="enabled">Yes</data> <data key="title">Flat Rate</data> diff --git a/app/code/Magento/Shipping/Test/Mftf/Metadata/shipping_methods-meta.xml b/app/code/Magento/Shipping/Test/Mftf/Metadata/shipping_methods-meta.xml index 8b6a2aab74580..5781b886386f6 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Metadata/shipping_methods-meta.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Metadata/shipping_methods-meta.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="FlatRateShippingMethodSetup" dataType="flat_rate_shipping_method" type="create" auth="adminFormKey" url="/admin/system_config/save/section/carriers/" method="POST"> <object key="groups" dataType="flat_rate_shipping_method"> <object key="flatrate" dataType="flat_rate_shipping_method"> diff --git a/app/code/Magento/Shipping/Test/Mftf/Page/AdminShipmentNewPage.xml b/app/code/Magento/Shipping/Test/Mftf/Page/AdminShipmentNewPage.xml index e6b3f1100dd88..597abb5694e30 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Page/AdminShipmentNewPage.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Page/AdminShipmentNewPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminShipmentNewPage" url="order_shipment/new/order_id/" area="admin" module="Shipping"> <section name="AdminShipmentMainActionsSection"/> <section name="AdminShipmentOrderInformationSection"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentAddressInformationSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentAddressInformationSection.xml index ea4dde8190bc7..14fefd981e4ed 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentAddressInformationSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentAddressInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentAddressInformationSection"> <element name="billingAddress" type="text" selector=".order-billing-address address"/> <element name="billingAddressEdit" type="button" selector=".order-billing-address .actions a"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml index 7c936a7420db9..a7bf82588f7c7 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml @@ -7,12 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentItemsSection"> - <element name="itemName" type="text" selector=".order-shipment-table tbody:nth-of-type({{row}}) .col-product .product-title" parameterized="true"/> - <element name="itemSku" type="text" selector=".order-shipment-table tbody:nth-of-type({{row}}) .col-product .product-sku-block" parameterized="true"/> - <element name="itemQty" type="text" selector=".order-shipment-table tbody:nth-of-type({{row}}) .col-ordered-qty .qty-table" parameterized="true"/> - <element name="itemQtyToShip" type="input" selector=".order-shipment-table tbody:nth-of-type({{row}}) .col-qty input.qty-item"/> + <element name="itemName" type="text" selector=".order-shipment-table tbody:nth-of-type({{var1}}) .col-product .product-title" parameterized="true"/> + <element name="itemSku" type="text" selector=".order-shipment-table tbody:nth-of-type({{var1}}) .col-product .product-sku-block" parameterized="true"/> + <element name="itemQty" type="text" selector=".order-shipment-table tbody:nth-of-type({{var1}}) .col-ordered-qty .qty-table" parameterized="true"/> + <element name="itemQtyToShip" type="input" selector=".order-shipment-table tbody:nth-of-type({{var1}}) .col-qty input.qty-item" parameterized="true"/> <element name="nameColumn" type="text" selector=".order-shipment-table .col-product .product-title"/> <element name="skuColumn" type="text" selector=".order-shipment-table .col-product .product-sku-block"/> </section> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentMainActionsSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentMainActionsSection.xml index 506a7a496d8d8..9f66b269b96ac 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentMainActionsSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentMainActionsSection"> <element name="submitShipment" type="button" selector="button.action-default.save.submit-button"/> </section> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentOrderInformationSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentOrderInformationSection.xml index 56f5a3221535b..ca2b867bc1f4c 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentOrderInformationSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentOrderInformationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentOrderInformationSection"> <element name="orderId" type="text" selector="div.order-information span.title > a" timeout="30"/> <element name="orderDate" type="text" selector=".order-information table.order-information-table tr:first-of-type > td"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentPaymentShippingSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentPaymentShippingSection.xml index e6004e8c59454..48c7106c2d65e 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentPaymentShippingSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentPaymentShippingSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentPaymentShippingSection"> <element name="PaymentMethod" type="text" selector=".order-payment-method .order-payment-method-title"/> <element name="CurrencyInformation" type="text" selector=".order-payment-method .order-payment-currency"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentTotalSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentTotalSection.xml index 2a4150b19a454..f2f39d77d8d79 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentTotalSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentTotalSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminShipmentTotalSection"> <element name="CommentText" type="textarea" selector="#shipment_comment_text"/> <element name="AppendComments" type="checkbox" selector=".order-totals input#notify_customer"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml index e14fed443ac0c..a0edf4e13a80f 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/EndToEndB2CAdminTest.xml @@ -6,7 +6,7 @@ */ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CAdminTest"> <!--Ship Order--> <comment userInput="Admin creates shipment" stepKey="adminCreatesShipmentComment" before="clickShipAction"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/composer.json b/app/code/Magento/Shipping/Test/Mftf/composer.json deleted file mode 100644 index 1b640d86f907d..0000000000000 --- a/app/code/Magento/Shipping/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-shipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-contact": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-fedex": "100.0.0-dev", - "magento/functional-test-module-ups": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Shipping/Test/Unit/Model/Carrier/AbstractCarrierOnlineTest.php b/app/code/Magento/Shipping/Test/Unit/Model/Carrier/AbstractCarrierOnlineTest.php index 6f87eb171a398..b40f5b26b89f1 100644 --- a/app/code/Magento/Shipping/Test/Unit/Model/Carrier/AbstractCarrierOnlineTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Model/Carrier/AbstractCarrierOnlineTest.php @@ -100,7 +100,7 @@ public function testComposePackages() $this->stockItemData->expects($this->atLeastOnce())->method('getIsDecimalDivided') ->will($this->returnValue(true)); - $this->carrier->proccessAdditionalValidation($request); + $this->carrier->processAdditionalValidation($request); } public function testParseXml() diff --git a/app/code/Magento/Shipping/view/frontend/templates/tracking/progress.phtml b/app/code/Magento/Shipping/view/frontend/templates/tracking/progress.phtml index 84126d8eeb636..237eba09ff802 100644 --- a/app/code/Magento/Shipping/view/frontend/templates/tracking/progress.phtml +++ b/app/code/Magento/Shipping/view/frontend/templates/tracking/progress.phtml @@ -23,7 +23,7 @@ $track = $block->getData('track'); </thead> <tbody> <?php foreach ($track->getProgressdetail() as $detail): ?> - <?php $detailDate = (!empty($detail['deliverydate']) ? $parentBlock->formatDeliveryDate($detail['deliverydate']) : ''); ?> + <?php $detailDate = (!empty($detail['deliverydate']) ? $parentBlock->formatDeliveryDate($detail['deliverydate'] . ' ' . $detail['deliverytime']) : ''); ?> <?php $detailTime = (!empty($detail['deliverytime']) ? $parentBlock->formatDeliveryTime($detail['deliverytime'], $detail['deliverydate']) : ''); ?> <tr> <td data-th="<?= $block->escapeHtml(__('Location')) ?>" class="col location"> diff --git a/app/code/Magento/Signifyd/Block/Fingerprint.php b/app/code/Magento/Signifyd/Block/Fingerprint.php index db76fc6c94468..f43bffce1fc1a 100644 --- a/app/code/Magento/Signifyd/Block/Fingerprint.php +++ b/app/code/Magento/Signifyd/Block/Fingerprint.php @@ -42,7 +42,7 @@ class Fingerprint extends Template * @var string * @since 100.2.0 */ - protected $_template = 'fingerprint.phtml'; + protected $_template = 'Magento_Signifyd::fingerprint.phtml'; /** * @param Context $context diff --git a/app/code/Magento/Signifyd/Controller/Webhooks/Handler.php b/app/code/Magento/Signifyd/Controller/Webhooks/Handler.php index 12bd773d35a2f..2dee31f4048b9 100644 --- a/app/code/Magento/Signifyd/Controller/Webhooks/Handler.php +++ b/app/code/Magento/Signifyd/Controller/Webhooks/Handler.php @@ -7,6 +7,8 @@ use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Signifyd\Api\CaseRepositoryInterface; use Magento\Signifyd\Model\CaseServices\UpdatingServiceFactory; @@ -21,7 +23,7 @@ * * @see https://www.signifyd.com/docs/api/#/reference/webhooks/ */ -class Handler extends Action +class Handler extends Action implements \Magento\Framework\App\CsrfAwareActionInterface { /** * Event topic of test webhook request. @@ -136,4 +138,20 @@ public function execute() $this->logger->critical($e); } } + + /** + * @inheritDoc + */ + public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException + { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } } diff --git a/app/code/Magento/Signifyd/Observer/PlaceOrder.php b/app/code/Magento/Signifyd/Observer/PlaceOrder.php index 8415bc006b8aa..7c451a129cccd 100644 --- a/app/code/Magento/Signifyd/Observer/PlaceOrder.php +++ b/app/code/Magento/Signifyd/Observer/PlaceOrder.php @@ -10,6 +10,7 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; use Magento\Signifyd\Api\CaseCreationServiceInterface; use Magento\Signifyd\Model\Config; use Psr\Log\LoggerInterface; @@ -80,7 +81,9 @@ public function execute(Observer $observer) private function createCaseForOrder($order) { $orderId = $order->getEntityId(); - if (null === $orderId || $order->getPayment()->getMethodInstance()->isOffline()) { + if (null === $orderId + || $order->getPayment()->getMethodInstance()->isOffline() + || $order->getState() === Order::STATE_PENDING_PAYMENT) { return; } diff --git a/app/code/Magento/Signifyd/Test/Mftf/composer.json b/app/code/Magento/Signifyd/Test/Mftf/composer.json deleted file mode 100644 index dc969cc4780ef..0000000000000 --- a/app/code/Magento/Signifyd/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-signifyd", - "description": "Submitting Case Entry to Signifyd on Order Creation", - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Signifyd/Test/Unit/Controller/Webhooks/HandlerTest.php b/app/code/Magento/Signifyd/Test/Unit/Controller/Webhooks/HandlerTest.php index 1a8cfdc703247..8b98be338b973 100644 --- a/app/code/Magento/Signifyd/Test/Unit/Controller/Webhooks/HandlerTest.php +++ b/app/code/Magento/Signifyd/Test/Unit/Controller/Webhooks/HandlerTest.php @@ -140,7 +140,7 @@ protected function setUp() } /** - * Successfull case + * Successful case */ public function testExecuteSuccessfully() { diff --git a/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php b/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php index 4e7edddf7b948..d63831b1d4a8e 100644 --- a/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php +++ b/app/code/Magento/Signifyd/Test/Unit/Observer/PlaceOrderTest.php @@ -10,6 +10,7 @@ use Magento\Framework\Exception\AlreadyExistsException; use Magento\Payment\Model\MethodInterface; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; use Magento\Signifyd\Api\CaseCreationServiceInterface; use Magento\Signifyd\Model\Config; @@ -193,6 +194,23 @@ public function testExecute() $this->placeOrder->execute($this->observer); } + public function testExecuteWithOrderPendingPayment() + { + $orderId = 1; + $storeId = 2; + + $this->withActiveSignifydIntegration(true, $storeId); + $this->withOrderEntity($orderId, $storeId); + $this->orderEntity->method('getState') + ->willReturn(Order::STATE_PENDING_PAYMENT); + $this->withAvailablePaymentMethod(true); + + $this->creationService->expects(self::never()) + ->method('createForOrder'); + + $this->placeOrder->execute($this->observer); + } + /** * Specifies order entity mock execution. * diff --git a/app/code/Magento/Signifyd/composer.json b/app/code/Magento/Signifyd/composer.json index c766ccd848ca4..992a74d0dc3ec 100644 --- a/app/code/Magento/Signifyd/composer.json +++ b/app/code/Magento/Signifyd/composer.json @@ -21,7 +21,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Signifyd/etc/adminhtml/system.xml b/app/code/Magento/Signifyd/etc/adminhtml/system.xml index 71f5916ca5325..2dd75d2d91e5b 100644 --- a/app/code/Magento/Signifyd/etc/adminhtml/system.xml +++ b/app/code/Magento/Signifyd/etc/adminhtml/system.xml @@ -11,7 +11,7 @@ <label>Fraud Protection</label> <tab>sales</tab> <resource>Magento_Sales::fraud_protection</resource> - <group id="signifyd" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> + <group id="signifyd" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0"> <fieldset_css>signifyd-logo-header</fieldset_css> <group id="about" translate="label comment" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> <frontend_model>Magento\Signifyd\Block\Adminhtml\System\Config\Fieldset\Info</frontend_model> @@ -52,7 +52,7 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <config_path>fraud_protection/signifyd/debug</config_path> </field> - <field id="webhook_url" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="webhook_url" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Webhook URL</label> <comment><![CDATA[Your webhook URL will be used to <a href="https://app.signifyd.com/settings/notifications" target="_blank">configure</a> a guarantee completed webhook in Signifyd. Webhooks are used to sync Signifyd`s guarantee decisions back to Magento.]]></comment> <attribute type="handler_url">signifyd/webhooks/handler</attribute> diff --git a/app/code/Magento/Signifyd/etc/db_schema_whitelist.json b/app/code/Magento/Signifyd/etc/db_schema_whitelist.json index 31f753dbb3b39..69d164b23d9e7 100644 --- a/app/code/Magento/Signifyd/etc/db_schema_whitelist.json +++ b/app/code/Magento/Signifyd/etc/db_schema_whitelist.json @@ -1,28 +1,28 @@ { - "signifyd_case": { - "column": { - "entity_id": true, - "order_id": true, - "case_id": true, - "guarantee_eligible": true, - "guarantee_disposition": true, - "status": true, - "score": true, - "associated_team": true, - "review_disposition": true, - "created_at": true, - "updated_at": true + "signifyd_case": { + "column": { + "entity_id": true, + "order_id": true, + "case_id": true, + "guarantee_eligible": true, + "guarantee_disposition": true, + "status": true, + "score": true, + "associated_team": true, + "review_disposition": true, + "created_at": true, + "updated_at": true + }, + "constraint": { + "PRIMARY": true, + "SIGNIFYD_CASE_ORDER_ID_SALES_ORDER_ENTITY_ID": true, + "SIGNIFYD_CASE_ORDER_ID": true, + "SIGNIFYD_CASE_CASE_ID": true + } }, - "constraint": { - "PRIMARY": true, - "SIGNIFYD_CASE_ORDER_ID_SALES_ORDER_ENTITY_ID": true, - "SIGNIFYD_CASE_ORDER_ID": true, - "SIGNIFYD_CASE_CASE_ID": true + "sales_order_grid": { + "column": { + "signifyd_guarantee_status": true + } } - }, - "sales_order_grid": { - "column": { - "signifyd_guarantee_status": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Signifyd/etc/events.xml b/app/code/Magento/Signifyd/etc/events.xml index a89b56ddf13c6..d5ba6e5a99227 100644 --- a/app/code/Magento/Signifyd/etc/events.xml +++ b/app/code/Magento/Signifyd/etc/events.xml @@ -12,4 +12,7 @@ <event name="paypal_express_place_order_success"> <observer name="signifyd_place_order_paypal_express_observer" instance="Magento\Signifyd\Observer\PlaceOrder"/> </event> + <event name="paypal_checkout_success"> + <observer name="signifyd_place_order_checkout_success_observer" instance="Magento\Signifyd\Observer\PlaceOrder" /> + </event> </config> diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php index f0be0fe7ab682..29d50ea8408fd 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php @@ -53,28 +53,32 @@ public function execute() $sitemap->load($id); // delete file $sitemapPath = $sitemap->getSitemapPath(); + if ($sitemapPath && $sitemapPath[0] === DIRECTORY_SEPARATOR) { + $sitemapPath = mb_substr($sitemapPath, 1); + } $sitemapFilename = $sitemap->getSitemapFilename(); - - $path = $directory->getRelativePath($sitemapPath . $sitemapFilename); + $path = $directory->getRelativePath( + $sitemapPath .$sitemapFilename + ); if ($sitemap->getSitemapFilename() && $directory->isFile($path)) { $directory->delete($path); } $sitemap->delete(); // display success message - $this->messageManager->addSuccess(__('You deleted the sitemap.')); + $this->messageManager->addSuccessMessage(__('You deleted the sitemap.')); // go to grid $this->_redirect('adminhtml/*/'); return; } catch (\Exception $e) { // display error message - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); // go back to edit form $this->_redirect('adminhtml/*/edit', ['sitemap_id' => $id]); return; } } // display error message - $this->messageManager->addError(__('We can\'t find a sitemap to delete.')); + $this->messageManager->addErrorMessage(__('We can\'t find a sitemap to delete.')); // go to grid $this->_redirect('adminhtml/*/'); } diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php index 04ab4f8725e0e..111353550b9cd 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php @@ -41,7 +41,7 @@ public function execute() if ($id) { $model->load($id); if (!$model->getId()) { - $this->messageManager->addError(__('This sitemap no longer exists.')); + $this->messageManager->addErrorMessage(__('This sitemap no longer exists.')); $this->_redirect('adminhtml/*/'); return; } diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php index 67d2ce4f4f148..9592ab6f57c55 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php @@ -6,8 +6,29 @@ */ namespace Magento\Sitemap\Controller\Adminhtml\Sitemap; +use Magento\Backend\App\Action; +use Magento\Store\Model\App\Emulation; +use Magento\Framework\App\ObjectManager; + class Generate extends \Magento\Sitemap\Controller\Adminhtml\Sitemap { + /** @var \Magento\Store\Model\App\Emulation $appEmulation */ + private $appEmulation; + + /** + * Generate constructor. + * @param Action\Context $context + * @param \Magento\Store\Model\App\Emulation|null $appEmulation + */ + public function __construct( + Action\Context $context, + Emulation $appEmulation = null + ) { + parent::__construct($context); + $this->appEmulation = $appEmulation ?: ObjectManager::getInstance() + ->get(\Magento\Store\Model\App\Emulation::class); + } + /** * Generate sitemap * @@ -23,18 +44,26 @@ public function execute() // if sitemap record exists if ($sitemap->getId()) { try { + //We need to emulate to get the correct frontend URL for the product images + $this->appEmulation->startEnvironmentEmulation( + $sitemap->getStoreId(), + \Magento\Framework\App\Area::AREA_FRONTEND, + true + ); $sitemap->generateXml(); - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( __('The sitemap "%1" has been generated.', $sitemap->getSitemapFilename()) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { - $this->messageManager->addException($e, __('We can\'t generate the sitemap right now.')); + $this->messageManager->addExceptionMessage($e, __('We can\'t generate the sitemap right now.')); + } finally { + $this->appEmulation->stopEnvironmentEmulation(); } } else { - $this->messageManager->addError(__('We can\'t find a sitemap to generate.')); + $this->messageManager->addErrorMessage(__('We can\'t find a sitemap to generate.')); } // go to grid diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php index 5c38cc68e6ef7..1e0d1cb248f00 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php @@ -30,7 +30,7 @@ protected function validatePath(array $data) $validator->setPaths($helper->getValidPaths()); if (!$validator->isValid($path)) { foreach ($validator->getMessages() as $message) { - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } // save data in session $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData($data); @@ -83,13 +83,13 @@ protected function saveData($data) // save the data $model->save(); // display success message - $this->messageManager->addSuccess(__('You saved the sitemap.')); + $this->messageManager->addSuccessMessage(__('You saved the sitemap.')); // clear previously saved data from session $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData(false); return $model->getId(); } catch (\Exception $e) { // display error message - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); // save data in session $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData($data); } diff --git a/app/code/Magento/Sitemap/Test/Mftf/composer.json b/app/code/Magento/Sitemap/Test/Mftf/composer.json deleted file mode 100644 index 46ad680c9fb1e..0000000000000 --- a/app/code/Magento/Sitemap/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-sitemap", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-robots": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/DeleteTest.php b/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/DeleteTest.php deleted file mode 100644 index ed004fe88b318..0000000000000 --- a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/DeleteTest.php +++ /dev/null @@ -1,149 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sitemap\Test\Unit\Controller\Adminhtml\Sitemap; - -class DeleteTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sitemap\Controller\Adminhtml\Sitemap\Delete - */ - private $deleteController; - - /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject - */ - private $filesystemMock; - - /** - * @var \Magento\Sitemap\Model\SitemapFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $sitemapFactoryMock; - - /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $responseMock; - - /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $messageManagerMock; - - protected function setUp() - { - $this->filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->sitemapFactoryMock = $this->getMockBuilder(\Magento\Sitemap\Model\SitemapFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMockForAbstractClass(); - $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\ResponseInterface::class) - ->disableOriginalConstructor() - ->setMethods(['setRedirect']) - ->getMockForAbstractClass(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMockForAbstractClass(); - - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->deleteController = $objectManagerHelper->getObject( - \Magento\Sitemap\Controller\Adminhtml\Sitemap\Delete::class, - [ - 'request' => $this->requestMock, - 'response' => $this->responseMock, - 'messageManager' => $this->messageManagerMock, - 'filesystem' => $this->filesystemMock, - 'sitemapFactory' => $this->sitemapFactoryMock - ] - ); - } - - public function testExecuteWithoutSitemapId() - { - $writeDirectoryMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\WriteInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->filesystemMock->expects($this->any())->method('getDirectoryWrite')->willReturn($writeDirectoryMock); - $this->requestMock->expects($this->any())->method('getParam')->with('sitemap_id')->willReturn(0); - $this->responseMock->expects($this->once())->method('setRedirect'); - $this->messageManagerMock->expects($this->any()) - ->method('addError') - ->with('We can\'t find a sitemap to delete.'); - - $this->deleteController->execute(); - } - - public function testExecuteCannotFindSitemap() - { - $id = 1; - $writeDirectoryMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\WriteInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->filesystemMock->expects($this->any())->method('getDirectoryWrite')->willReturn($writeDirectoryMock); - $this->requestMock->expects($this->any())->method('getParam')->with('sitemap_id')->willReturn($id); - - $sitemapMock = $this->getMockBuilder(\Magento\Sitemap\Model\Sitemap::class) - ->disableOriginalConstructor() - ->setMethods(['load']) - ->getMock(); - - $sitemapMock->expects($this->once())->method('load')->with($id)->willThrowException(new \Exception); - $this->sitemapFactoryMock->expects($this->once())->method('create')->willReturn($sitemapMock); - $this->responseMock->expects($this->once())->method('setRedirect'); - $this->messageManagerMock->expects($this->any()) - ->method('addError'); - - $this->deleteController->execute(); - } - - public function testExecute() - { - $id = 1; - $sitemapPath = '/'; - $sitemapFilename = 'sitemap.xml'; - $relativePath = '/sitemap.xml'; - $writeDirectoryMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\WriteInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->filesystemMock->expects($this->any())->method('getDirectoryWrite')->willReturn($writeDirectoryMock); - $this->requestMock->expects($this->any())->method('getParam')->with('sitemap_id')->willReturn($id); - - $sitemapMock = $this->getMockBuilder(\Magento\Sitemap\Model\Sitemap::class) - ->disableOriginalConstructor() - ->setMethods(['getSitemapPath', 'getSitemapFilename', 'load', 'delete']) - ->getMock(); - $sitemapMock->expects($this->once())->method('load')->with($id); - $sitemapMock->expects($this->once())->method('delete'); - $sitemapMock->expects($this->any())->method('getSitemapPath')->willReturn($sitemapPath); - $sitemapMock->expects($this->any())->method('getSitemapFilename')->willReturn($sitemapFilename); - $this->sitemapFactoryMock->expects($this->once())->method('create')->willReturn($sitemapMock); - $writeDirectoryMock->expects($this->any()) - ->method('getRelativePath') - ->with($sitemapPath . $sitemapFilename) - ->willReturn($relativePath); - $writeDirectoryMock->expects($this->once())->method('isFile')->with($relativePath)->willReturn(true); - $writeDirectoryMock->expects($this->once())->method('delete')->with($relativePath)->willReturn(true); - - $this->responseMock->expects($this->once())->method('setRedirect'); - $this->messageManagerMock->expects($this->any()) - ->method('addSuccess'); - - $this->deleteController->execute(); - } -} diff --git a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php b/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php index 36e3aa0312627..f77954101df7c 100644 --- a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php +++ b/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php @@ -154,7 +154,7 @@ public function testTryToSaveInvalidDataShouldFailWithErrors() ->willReturnMap([[$helperClass, $helper], [$sessionClass, $session]]); $this->messageManagerMock->expects($this->at(0)) - ->method('addError') + ->method('addErrorMessage') ->withConsecutive( [$messages[0]], [$messages[1]] diff --git a/app/code/Magento/Sitemap/etc/db_schema_whitelist.json b/app/code/Magento/Sitemap/etc/db_schema_whitelist.json index b7067f773b93f..b6359e7526a0f 100644 --- a/app/code/Magento/Sitemap/etc/db_schema_whitelist.json +++ b/app/code/Magento/Sitemap/etc/db_schema_whitelist.json @@ -1,19 +1,19 @@ { - "sitemap": { - "column": { - "sitemap_id": true, - "sitemap_type": true, - "sitemap_filename": true, - "sitemap_path": true, - "sitemap_time": true, - "store_id": true - }, - "index": { - "SITEMAP_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "SITEMAP_STORE_ID_STORE_STORE_ID": true + "sitemap": { + "column": { + "sitemap_id": true, + "sitemap_type": true, + "sitemap_filename": true, + "sitemap_path": true, + "sitemap_time": true, + "store_id": true + }, + "index": { + "SITEMAP_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "SITEMAP_STORE_ID_STORE_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Store/App/Config/Type/Scopes.php b/app/code/Magento/Store/App/Config/Type/Scopes.php index 9fbecc4db303e..e6b9d0000e4a6 100644 --- a/app/code/Magento/Store/App/Config/Type/Scopes.php +++ b/app/code/Magento/Store/App/Config/Type/Scopes.php @@ -114,5 +114,6 @@ private function convertIdPathToCodePath(array $patchChunks) public function clean() { $this->data = null; + $this->idCodeMap = []; } } diff --git a/app/code/Magento/Store/App/Response/Redirect.php b/app/code/Magento/Store/App/Response/Redirect.php index d826ad3425f54..534c6be41c937 100644 --- a/app/code/Magento/Store/App/Response/Redirect.php +++ b/app/code/Magento/Store/App/Response/Redirect.php @@ -77,21 +77,21 @@ public function __construct( /** * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _getUrl() { $refererUrl = $this->_request->getServer('HTTP_REFERER'); - $url = (string)$this->_request->getParam(self::PARAM_NAME_REFERER_URL); - if ($url) { - $refererUrl = $url; - } - $url = $this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_BASE64_URL); - if ($url) { - $refererUrl = $this->_urlCoder->decode($url); - } - $url = $this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED); - if ($url) { - $refererUrl = $this->_urlCoder->decode($url); + $encodedUrl = $this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED) + ?: $this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_BASE64_URL); + + if ($encodedUrl) { + $refererUrl = $this->_urlCoder->decode($encodedUrl); + } else { + $url = (string)$this->_request->getParam(self::PARAM_NAME_REFERER_URL); + if ($url) { + $refererUrl = $url; + } } if (!$this->_isUrlInternal($refererUrl)) { @@ -171,16 +171,6 @@ public function success($defaultUrl) */ public function updatePathParams(array $arguments) { - if ($this->_session->getCookieShouldBeReceived() - && $this->_sidResolver->getUseSessionInUrl() - && $this->_canUseSessionIdInParam - ) { - $arguments += [ - '_query' => [ - $this->_sidResolver->getSessionIdQueryParam($this->_session) => $this->_session->getSessionId(), - ] - ]; - } return $arguments; } diff --git a/app/code/Magento/Store/Block/Switcher.php b/app/code/Magento/Store/Block/Switcher.php index b0659b7caf7e8..58a7ba3c1c283 100644 --- a/app/code/Magento/Store/Block/Switcher.php +++ b/app/code/Magento/Store/Block/Switcher.php @@ -13,6 +13,9 @@ use Magento\Store\Api\StoreResolverInterface; use Magento\Store\Model\Group; use Magento\Store\Model\Store; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Url\Helper\Data as UrlHelper; /** * @api @@ -30,20 +33,28 @@ class Switcher extends \Magento\Framework\View\Element\Template */ protected $_postDataHelper; + /** + * @var UrlHelper + */ + private $urlHelper; + /** * Constructs * * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Data\Helper\PostHelper $postDataHelper * @param array $data + * @param UrlHelper $urlHelper */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Framework\Data\Helper\PostHelper $postDataHelper, - array $data = [] + array $data = [], + UrlHelper $urlHelper = null ) { $this->_postDataHelper = $postDataHelper; parent::__construct($context, $data); + $this->urlHelper = $urlHelper ?: ObjectManager::getInstance()->get(UrlHelper::class); } /** @@ -222,15 +233,20 @@ public function getStoreName() * @param Store $store * @param array $data * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getTargetStorePostData(Store $store, $data = []) { - $data[StoreResolverInterface::PARAM_NAME] = $store->getCode(); + $data[\Magento\Store\Api\StoreResolverInterface::PARAM_NAME] = $store->getCode(); + $data['___from_store'] = $this->_storeManager->getStore()->getCode(); + + $urlOnTargetStore = $store->getCurrentUrl(false); + $data[ActionInterface::PARAM_NAME_URL_ENCODED] = $this->urlHelper->getEncodedUrl($urlOnTargetStore); + + $url = $this->getUrl('stores/store/redirect'); - //We need to set fromStore argument as true because - //it will enable proper URL rewriting during store switching. return $this->_postDataHelper->getPostData( - $store->getCurrentUrl(true), + $url, $data ); } diff --git a/app/code/Magento/Store/Controller/Store/Redirect.php b/app/code/Magento/Store/Controller/Store/Redirect.php new file mode 100644 index 0000000000000..21692e9d6dd1e --- /dev/null +++ b/app/code/Magento/Store/Controller/Store/Redirect.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Controller\Store; + +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\StoreResolver; +use Magento\Framework\Session\SidResolverInterface; +use Magento\Framework\Session\Generic as Session; + +/** + * Builds correct url to target store and performs redirect. + */ +class Redirect extends \Magento\Framework\App\Action\Action +{ + /** + * @var StoreRepositoryInterface + */ + private $storeRepository; + + /** + * @var StoreResolverInterface + */ + private $storeResolver; + + /** + * @var SidResolverInterface + */ + private $sidResolver; + + /** + * @var Session + */ + private $session; + + /** + * @param Context $context + * @param StoreRepositoryInterface $storeRepository + * @param StoreResolverInterface $storeResolver + * @param Session $session + * @param SidResolverInterface $sidResolver + */ + public function __construct( + Context $context, + StoreRepositoryInterface $storeRepository, + StoreResolverInterface $storeResolver, + Session $session, + SidResolverInterface $sidResolver + ) { + parent::__construct($context); + $this->storeRepository = $storeRepository; + $this->storeResolver = $storeResolver; + $this->session = $session; + $this->sidResolver = $sidResolver; + } + + /** + * @return ResponseInterface|\Magento\Framework\Controller\ResultInterface + * @throws NoSuchEntityException + */ + public function execute() + { + /** @var \Magento\Store\Model\Store $currentStore */ + $currentStore = $this->storeRepository->getById($this->storeResolver->getCurrentStoreId()); + $targetStoreCode = $this->_request->getParam(StoreResolver::PARAM_NAME); + $fromStoreCode = $this->_request->getParam('___from_store'); + $error = null; + + if ($targetStoreCode === null) { + return $this->_redirect($currentStore->getBaseUrl()); + } + + try { + /** @var \Magento\Store\Model\Store $targetStore */ + $fromStore = $this->storeRepository->get($fromStoreCode); + } catch (NoSuchEntityException $e) { + $error = __('Requested store is not found'); + } + + if ($error !== null) { + $this->messageManager->addErrorMessage($error); + $this->_redirect->redirect($this->_response, $currentStore->getBaseUrl()); + } else { + $encodedUrl = $this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED); + + $query = [ + '___from_store' => $fromStore->getCode(), + StoreResolverInterface::PARAM_NAME => $targetStoreCode, + \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => $encodedUrl, + ]; + + if ($this->sidResolver->getUseSessionInUrl()) { + // allow customers to stay logged in during store switching + $sidName = $this->sidResolver->getSessionIdQueryParam($this->session); + $query[$sidName] = $this->session->getSessionId(); + } + + $arguments = [ + '_nosid' => true, + '_query' => $query + ]; + $this->_redirect->redirect($this->_response, 'stores/store/switch', $arguments); + } + } +} diff --git a/app/code/Magento/Store/Controller/Store/SwitchAction.php b/app/code/Magento/Store/Controller/Store/SwitchAction.php index f2872a51db6f4..df946b45d60e5 100644 --- a/app/code/Magento/Store/Controller/Store/SwitchAction.php +++ b/app/code/Magento/Store/Controller/Store/SwitchAction.php @@ -10,16 +10,19 @@ use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context as ActionContext; use Magento\Framework\App\Http\Context as HttpContext; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Api\StoreCookieManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Store\Model\Store; use Magento\Store\Model\StoreIsInactiveException; use Magento\Store\Model\StoreResolver; use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\StoreSwitcher; +use Magento\Store\Model\StoreSwitcherInterface; /** - * Switch current store view. + * Handles store switching url and makes redirect. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SwitchAction extends Action { @@ -30,6 +33,7 @@ class SwitchAction extends Action /** * @var HttpContext + * @deprecated */ protected $httpContext; @@ -40,9 +44,15 @@ class SwitchAction extends Action /** * @var StoreManagerInterface + * @deprecated */ protected $storeManager; + /** + * @var StoreSwitcherInterface + */ + private $storeSwitcher; + /** * Initialize dependencies. * @@ -51,69 +61,55 @@ class SwitchAction extends Action * @param HttpContext $httpContext * @param StoreRepositoryInterface $storeRepository * @param StoreManagerInterface $storeManager + * @param StoreSwitcherInterface $storeSwitcher */ public function __construct( ActionContext $context, StoreCookieManagerInterface $storeCookieManager, HttpContext $httpContext, StoreRepositoryInterface $storeRepository, - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + StoreSwitcherInterface $storeSwitcher = null ) { parent::__construct($context); $this->storeCookieManager = $storeCookieManager; $this->httpContext = $httpContext; $this->storeRepository = $storeRepository; $this->storeManager = $storeManager; + $this->messageManager = $context->getMessageManager(); + $this->storeSwitcher = $storeSwitcher ?: ObjectManager::getInstance()->get(StoreSwitcherInterface::class); } /** * @return void + * @throws StoreSwitcher\CannotSwitchStoreException */ public function execute() { - $currentActiveStore = $this->storeManager->getStore(); - $storeCode = $this->_request->getParam( + $targetStoreCode = $this->_request->getParam( StoreResolver::PARAM_NAME, $this->storeCookieManager->getStoreCodeFromCookie() ); + $fromStoreCode = $this->_request->getParam('___from_store'); + $requestedUrlToRedirect = $this->_redirect->getRedirectUrl(); + $redirectUrl = $requestedUrlToRedirect; + + $error = null; try { - $store = $this->storeRepository->getActiveStoreByCode($storeCode); + $fromStore = $this->storeRepository->get($fromStoreCode); + $targetStore = $this->storeRepository->getActiveStoreByCode($targetStoreCode); } catch (StoreIsInactiveException $e) { $error = __('Requested store is inactive'); } catch (NoSuchEntityException $e) { $error = __("The store that was requested wasn't found. Verify the store and try again."); } - - if (isset($error)) { - $this->messageManager->addError($error); - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); - return; - } - - $defaultStoreView = $this->storeManager->getDefaultStoreView(); - if ($defaultStoreView->getId() == $store->getId()) { - $this->storeCookieManager->deleteStoreCookie($store); + if ($error !== null) { + $this->messageManager->addErrorMessage($error); } else { - $this->httpContext->setValue(Store::ENTITY, $store->getCode(), $defaultStoreView->getCode()); - $this->storeCookieManager->setStoreCookie($store); + $redirectUrl = $this->storeSwitcher->switch($fromStore, $targetStore, $requestedUrlToRedirect); } - if ($store->isUseStoreInUrl()) { - // Change store code in redirect url - if (strpos($this->_redirect->getRedirectUrl(), $currentActiveStore->getBaseUrl()) !== false) { - $this->getResponse()->setRedirect( - str_replace( - $currentActiveStore->getBaseUrl(), - $store->getBaseUrl(), - $this->_redirect->getRedirectUrl() - ) - ); - } else { - $this->getResponse()->setRedirect($store->getBaseUrl()); - } - } else { - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); - } + $this->getResponse()->setRedirect($redirectUrl); } } diff --git a/app/code/Magento/Store/Model/Indexer/WebsiteDimensionProvider.php b/app/code/Magento/Store/Model/Indexer/WebsiteDimensionProvider.php new file mode 100644 index 0000000000000..6fceeabc3597e --- /dev/null +++ b/app/code/Magento/Store/Model/Indexer/WebsiteDimensionProvider.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model\Indexer; + +use Magento\Framework\Indexer\Dimension; +use Magento\Store\Model\ResourceModel\Website\CollectionFactory as WebsiteCollectionFactory; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Indexer\DimensionProviderInterface; +use Magento\Store\Model\Store; + +class WebsiteDimensionProvider implements DimensionProviderInterface +{ + /** + * Name for website dimension for multidimensional indexer + * 'ws' - stands for 'website_store' + */ + const DIMENSION_NAME = 'ws'; + + /** + * @var WebsiteCollectionFactory + */ + private $collectionFactory; + + /** + * @var \SplFixedArray + */ + private $websitesDataIterator; + + /** + * @var DimensionFactory + */ + private $dimensionFactory; + + /** + * @param WebsiteCollectionFactory $collectionFactory + * @param DimensionFactory $dimensionFactory + */ + public function __construct(WebsiteCollectionFactory $collectionFactory, DimensionFactory $dimensionFactory) + { + $this->dimensionFactory = $dimensionFactory; + $this->collectionFactory = $collectionFactory; + } + + /** + * @return Dimension[]|\Traversable + */ + public function getIterator(): \Traversable + { + foreach ($this->getWebsites() as $website) { + yield $this->dimensionFactory->create(self::DIMENSION_NAME, (string)$website); + } + } + + /** + * @return array + */ + private function getWebsites(): array + { + if ($this->websitesDataIterator === null) { + $websites = $this->collectionFactory->create() + ->addFieldToFilter('code', ['neq' => Store::ADMIN_CODE]) + ->getAllIds(); + $this->websitesDataIterator = is_array($websites) ? $websites : []; + } + + return $this->websitesDataIterator; + } +} diff --git a/app/code/Magento/Store/Model/Plugin/StoreCookie.php b/app/code/Magento/Store/Model/Plugin/StoreCookie.php index 612d35e136d7e..9fca86ab71762 100644 --- a/app/code/Magento/Store/Model/Plugin/StoreCookie.php +++ b/app/code/Magento/Store/Model/Plugin/StoreCookie.php @@ -82,12 +82,5 @@ public function beforeDispatch( $this->storeCookieManager->deleteStoreCookie($this->storeManager->getDefaultStoreView()); } } - if ($this->storeCookieManager->getStoreCodeFromCookie() === null - || $request->getParam(StoreResolverInterface::PARAM_NAME) !== null - ) { - $storeId = $this->storeResolver->getCurrentStoreId(); - $store = $this->storeRepository->getActiveStoreById($storeId); - $this->storeCookieManager->setStoreCookie($store); - } } } diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 6e749df6a768d..af25957257421 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -1163,7 +1163,7 @@ public function isDefault() /** * Retrieve current url for store * - * @param bool|string $fromStore + * @param bool $fromStore * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1223,7 +1223,7 @@ public function getCurrentUrl($fromStore = true) . (isset($storeParsedUrl['port']) ? ':' . $storeParsedUrl['port'] : '') . $storeParsedUrl['path'] . $requestStringPath - . ($currentUrlQueryParams ? '?' . http_build_query($currentUrlQueryParams, '', '&') : ''); + . ($currentUrlQueryParams ? '?' . http_build_query($currentUrlQueryParams) : ''); return $currentUrl; } @@ -1379,7 +1379,8 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @param \Magento\Store\Api\Data\StoreExtensionInterface $extensionAttributes + * @return $this */ public function setExtensionAttributes( \Magento\Store\Api\Data\StoreExtensionInterface $extensionAttributes diff --git a/app/code/Magento/Store/Model/StoreDimensionProvider.php b/app/code/Magento/Store/Model/StoreDimensionProvider.php index 5bc019ea6835f..17f75fb863d9c 100644 --- a/app/code/Magento/Store/Model/StoreDimensionProvider.php +++ b/app/code/Magento/Store/Model/StoreDimensionProvider.php @@ -8,7 +8,7 @@ namespace Magento\Store\Model; use Magento\Framework\Indexer\DimensionFactory; -use Magento\Framework\Indexer\Dimension\DimensionProviderInterface; +use Magento\Framework\Indexer\DimensionProviderInterface; /** * Provide a list of stores as Dimension @@ -47,7 +47,7 @@ public function __construct(StoreManagerInterface $storeManager, DimensionFactor public function getIterator(): \Traversable { foreach (array_keys($this->storeManager->getStores()) as $storeId) { - yield [self::DIMENSION_NAME => $this->dimensionFactory->create(self::DIMENSION_NAME, $storeId)]; + yield [self::DIMENSION_NAME => $this->dimensionFactory->create(self::DIMENSION_NAME, (string)$storeId)]; } } } diff --git a/app/code/Magento/Store/Model/StoreSwitcher.php b/app/code/Magento/Store/Model/StoreSwitcher.php new file mode 100644 index 0000000000000..a294b6a0d6b0a --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcher.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model; + +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreSwitcher\CannotSwitchStoreException; + +/** + * Handles store switching procedure and detects url for final redirect after store switching. + */ +class StoreSwitcher implements StoreSwitcherInterface +{ + /** + * @var StoreSwitcherInterface[] + */ + private $storeSwitchers; + + /** + * @param StoreSwitcherInterface[] $storeSwitchers + * @throws \Exception + */ + public function __construct(array $storeSwitchers) + { + foreach ($storeSwitchers as $switcherName => $switcherInstance) { + if (!$switcherInstance instanceof StoreSwitcherInterface) { + throw new \InvalidArgumentException( + "Store switcher '{$switcherName}' is expected to implement interface " + . StoreSwitcherInterface::class + ); + } + } + $this->storeSwitchers = $storeSwitchers; + } + + /** + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string url to be redirected after switching + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws CannotSwitchStoreException + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string + { + $targetUrl = $redirectUrl; + + foreach ($this->storeSwitchers as $storeSwitcher) { + $targetUrl = $storeSwitcher->switch($fromStore, $targetStore, $targetUrl); + } + + return $targetUrl; + } +} diff --git a/app/code/Magento/Store/Model/StoreSwitcher/CannotSwitchStoreException.php b/app/code/Magento/Store/Model/StoreSwitcher/CannotSwitchStoreException.php new file mode 100644 index 0000000000000..c6afd71cb6f01 --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcher/CannotSwitchStoreException.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model\StoreSwitcher; + +use Magento\Framework\Exception\RuntimeException; +use Magento\Framework\Phrase; + +/** + * Exception thrown if store cannot be switched. + */ +class CannotSwitchStoreException extends RuntimeException +{ + /** + * @param \Exception|null $cause + * @param Phrase|null $phrase + * @param int $code + */ + public function __construct(\Exception $cause = null, Phrase $phrase = null, int $code = 0) + { + parent::__construct($phrase ?: __('The store cannot be switched.'), $cause, $code); + } +} diff --git a/app/code/Magento/Store/Model/StoreSwitcher/CleanTargetUrl.php b/app/code/Magento/Store/Model/StoreSwitcher/CleanTargetUrl.php new file mode 100644 index 0000000000000..3328a21e8f5e1 --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcher/CleanTargetUrl.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model\StoreSwitcher; + +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\StoreSwitcherInterface; +use Magento\Framework\Url\Helper\Data as UrlHelper; + +/** + * Remove SID, from_store, store from target url. + */ +class CleanTargetUrl implements StoreSwitcherInterface +{ + /** + * @var UrlHelper + */ + private $urlHelper; + + /** + * @var \Magento\Framework\Session\Generic + */ + private $session; + + /** + * @var \Magento\Framework\Session\SidResolverInterface + */ + private $sidResolver; + + /** + * @param UrlHelper $urlHelper + * @param \Magento\Framework\Session\Generic $session + * @param \Magento\Framework\Session\SidResolverInterface $sidResolver + */ + public function __construct( + UrlHelper $urlHelper, + \Magento\Framework\Session\Generic $session, + \Magento\Framework\Session\SidResolverInterface $sidResolver + ) { + $this->urlHelper = $urlHelper; + $this->session = $session; + $this->sidResolver = $sidResolver; + } + + /** + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string redirect url + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string + { + $targetUrl = $redirectUrl; + $sidName = $this->sidResolver->getSessionIdQueryParam($this->session); + $targetUrl = $this->urlHelper->removeRequestParam($targetUrl, $sidName); + $targetUrl = $this->urlHelper->removeRequestParam($targetUrl, '___from_store'); + $targetUrl = $this->urlHelper->removeRequestParam($targetUrl, StoreResolverInterface::PARAM_NAME); + + return $targetUrl; + } +} diff --git a/app/code/Magento/Store/Model/StoreSwitcher/ManagePrivateContent.php b/app/code/Magento/Store/Model/StoreSwitcher/ManagePrivateContent.php new file mode 100644 index 0000000000000..8aed785641efe --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcher/ManagePrivateContent.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model\StoreSwitcher; + +use Magento\Store\Model\StoreSwitcherInterface; +use Magento\Store\Api\Data\StoreInterface; + +/** + * Set private content cookie to have actual local storage data on target store after store switching. + */ +class ManagePrivateContent implements StoreSwitcherInterface +{ + /** + * @var \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory + */ + private $cookieMetadataFactory; + + /** + * @var \Magento\Framework\Stdlib\CookieManagerInterface + */ + private $cookieManager; + + /** + * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory + * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager + */ + public function __construct( + \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory, + \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager + ) { + $this->cookieMetadataFactory = $cookieMetadataFactory; + $this->cookieManager = $cookieManager; + } + + /** + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string redirect url + * @throws CannotSwitchStoreException + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string + { + try { + $publicCookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata() + ->setDurationOneYear() + ->setPath('/') + ->setSecure(false) + ->setHttpOnly(false); + $this->cookieManager->setPublicCookie( + \Magento\Framework\App\PageCache\Version::COOKIE_NAME, + 'should_be_updated', + $publicCookieMetadata + ); + } catch (\Exception $e) { + throw new CannotSwitchStoreException($e); + } + + return $redirectUrl; + } +} diff --git a/app/code/Magento/Store/Model/StoreSwitcher/ManageStoreCookie.php b/app/code/Magento/Store/Model/StoreSwitcher/ManageStoreCookie.php new file mode 100644 index 0000000000000..7a485be090af6 --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcher/ManageStoreCookie.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model\StoreSwitcher; + +use Magento\Store\Model\StoreSwitcherInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Framework\App\Http\Context as HttpContext; +use Magento\Store\Api\StoreCookieManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Store; + +/** + * Manage store cookie depending on what store view customer is. + */ +class ManageStoreCookie implements StoreSwitcherInterface +{ + /** + * @var StoreCookieManagerInterface + */ + private $storeCookieManager; + + /** + * @var HttpContext + */ + private $httpContext; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param StoreCookieManagerInterface $storeCookieManager + * @param HttpContext $httpContext + * @param StoreManagerInterface $storeManager + */ + public function __construct( + StoreCookieManagerInterface $storeCookieManager, + HttpContext $httpContext, + StoreManagerInterface $storeManager + ) { + $this->storeCookieManager = $storeCookieManager; + $this->httpContext = $httpContext; + $this->storeManager = $storeManager; + } + + /** + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string redirect url + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string + { + $defaultStoreView = $this->storeManager->getDefaultStoreView(); + if ($defaultStoreView !== null) { + if ($defaultStoreView->getId() === $targetStore->getId()) { + $this->storeCookieManager->deleteStoreCookie($targetStore); + } else { + $this->httpContext->setValue(Store::ENTITY, $targetStore->getCode(), $defaultStoreView->getCode()); + $this->storeCookieManager->setStoreCookie($targetStore); + } + } + + return $redirectUrl; + } +} diff --git a/app/code/Magento/Store/Model/StoreSwitcherInterface.php b/app/code/Magento/Store/Model/StoreSwitcherInterface.php new file mode 100644 index 0000000000000..ee6fb0b4763c6 --- /dev/null +++ b/app/code/Magento/Store/Model/StoreSwitcherInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model; + +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreSwitcher\CannotSwitchStoreException; + +/** + * Handles store switching procedure and detects url for final redirect after store switching. + */ +interface StoreSwitcherInterface +{ + /** + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string url to be redirected after switching + * @throws CannotSwitchStoreException + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string; +} diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml index e67e21e4c1e67..91fe4fccddb91 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Admin creates new Store group --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateNewStoreGroupActionGroup"> <arguments> <argument name="website" type="string"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml new file mode 100644 index 0000000000000..b21c79692a7cf --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreGroupActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateStoreGroupActionGroup"> + <arguments> + <argument name="Website" defaultValue="_defaultWebsite"/> + </arguments> + <amOnPage url="{{AdminSystemStoreGroupPage.url}}" stepKey="navigateToNewStoreGroup"/> + <waitForPageLoad stepKey="waitForStoreGroupPageLoad" /> + + <comment userInput="Creating Store Group" stepKey="storeGroupCreationComment" /> + <selectOption selector="{{AdminNewStoreGroupSection.storeGrpWebsiteDropdown}}" userInput="{{Website.name}}" stepKey="selectWebsite" /> + <fillField selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" userInput="{{CustomStoreGroupCustomWebsite.name}}" stepKey="enterStoreGroupName" /> + <fillField selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" userInput="{{CustomStoreGroupCustomWebsite.code}}" stepKey="enterStoreGroupCode" /> + <selectOption selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" userInput="Default Category" stepKey="setRootCategory" /> + <click selector="{{AdminNewStoreGroupActionsSection.saveButton}}" stepKey="clickSaveStoreGroup" /> + <waitForElementVisible selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="waitForPageReload"/> + <see userInput="You saved the store." stepKey="seeSavedMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml index b35b36bc667a7..99ca7991e5e90 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Test XML Example --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateStoreViewActionGroup"> <arguments> <argument name="StoreGroup" defaultValue="_defaultStoreGroup"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml index 709cfe8ec9c40..a87356303c6e8 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Admin creates new custom website --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateWebsiteActionGroup"> <arguments> <argument name="newWebsiteName" type="string"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml index 1267ebf8f440c..849dc91efedb7 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Test XML Example --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminDeleteStoreViewActionGroup"> <arguments> <argument name="customStore" defaultValue="customStore"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml index 0512a1f6fbc3f..1400fbf12c16c 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminDeleteWebsiteActionGroup"> <arguments> <argument name="websiteName" type="string"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml index bd004f1fc7de3..860f094a48ecc 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Test XML Example --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminSwitchStoreViewActionGroup"> <arguments> <argument name="storeView" defaultValue="customStore.name"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml index 1e23a85a78935..31bbe7550e5a1 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateCustomStoreViewActionGroup"> <arguments> <argument name="storeGroupName" defaultValue="customStoreGroup.name"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml index 0cac9bbd9954b..8e32b819aa954 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteCustomStoreActionGroup"> <arguments> <argument name="storeGroupName" defaultValue="customStoreGroup.name"/> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml new file mode 100644 index 0000000000000..cc6a1fb62ea5f --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteCustomWebsiteActionGroup"> + <arguments> + <argument name="websiteName" defaultValue="customWebsite.name"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnTheStorePage"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="clickOnResetButton"/> + <waitForPageLoad stepKey="waitForPageLoadAfterResetButtonClicked" time="10"/> + <fillField userInput="{{websiteName}}" selector="{{AdminStoresGridSection.websiteFilterTextField}}" stepKey="fillSearchWebsiteField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton" /> + <waitForPageLoad stepKey="waitForPageLoadAfterSearch" time="10"/> + <see userInput="{{websiteName}}" selector="{{AdminStoresGridSection.websiteNameInFirstRow}}" stepKey="verifyThatCorrectWebsiteFound"/> + <click selector="{{AdminStoresGridSection.websiteNameInFirstRow}}" stepKey="clickEditExistingWebsite"/> + + <click selector="{{AdminStoresMainActionsSection.deleteButton}}" stepKey="clickDeleteWebsiteButtonOnEditStorePage"/> + <selectOption userInput="No" selector="{{AdminStoresDeleteWebsiteSection.createDbBackup}}" stepKey="setCreateDbBackupToNo"/> + <click selector="{{AdminStoresDeleteWebsiteSection.deleteButton}}" stepKey="clickDeleteButtonOnDeleteWebsitePage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml index d14de9af9c14a..cfcd25086e067 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml @@ -7,7 +7,7 @@ --> <!-- Test XML Example --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontSwitchStoreViewActionGroup"> <arguments> <argument name="storeView" defaultValue="customStore"/> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 036069977bf52..2bff20e05435a 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultStore" type="store"> <data key="name">Default Store View</data> <data key="code">default</data> @@ -18,7 +18,7 @@ <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> - <data key="store_type">group</data> + <data key="store_type">store</data> <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreEN" type="store"> @@ -27,7 +27,7 @@ <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> - <data key="store_type">group</data> + <data key="store_type">store</data> <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreFR" type="store"> @@ -39,4 +39,14 @@ <data key="store_type">group</data> <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> + <entity name="staticStore" type="store"> + <!--data key="group_id">customStoreGroup.id</data--> + <data key="name" >Second Store View</data> + <data key="code" >store123</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml index 50139a0a87862..83ca12875d099 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultStoreGroup" type="group"> <data key="name">Main Website Store</data> <data key="code">main_website_store</data> @@ -21,4 +21,10 @@ <data key="store_action">add</data> <data key="store_type">group</data> </entity> + <entity name="staticStoreGroup" type="group"> + <data key="name">NewStore</data> + <data key="code">Base12</data> + <data key="root_category_id">2</data> + <data key="website_id">1</data> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml new file mode 100644 index 0000000000000..ee137d78d7fd2 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="_defaultWebsite" type="website"> + <data key="name">Main Website</data> + <data key="code">base</data> + <data key="sort_order">0</data> + </entity> + <entity name="customWebsite" type="website"> + <data key="name" unique="suffix">Second Website</data> + <data key="code" unique="suffix">second_website</data> + <data key="sort_order">10</data> + <data key="store_action">add</data> + <data key="store_type">website</data> + <data key="website_id">null</data> + </entity> +</entities> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store-meta.xml index e0263b2c88869..a1cfc69ecd705 100644 --- a/app/code/Magento/Store/Test/Mftf/Metadata/store-meta.xml +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateStore" dataType="store" type="create" auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" returnRegex="" > <object dataType="store" key="store"> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_group-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_group-meta.xml index bc117756a542b..a8d8ff7b68323 100644 --- a/app/code/Magento/Store/Test/Mftf/Metadata/store_group-meta.xml +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_group-meta.xml @@ -6,7 +6,7 @@ */ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateStoreGroup" dataType="group" type="create" auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" returnRegex="" > <contentType>application/x-www-form-urlencoded</contentType> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml new file mode 100644 index 0000000000000..bad274501f710 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateWebsite" dataType="website" type="create" + auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" returnRegex=""> + <object dataType="website" key="website"> + <field key="website_id">string</field> + <field key="name">string</field> + <field key="code">string</field> + <field key="sort_order">integer</field> + </object> + <field key="store_action">string</field> + <field key="store_type">string</field> + </operation> +</operations> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreDeletePage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreDeletePage.xml index 3ec22d3135137..79472f1cc2899 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreDeletePage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreDeletePage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreDeletePage" url="system_store/deleteStore" module="Magento_Store" area="admin"> <section name="AdminStoreBackupOptionsSection"/> </page> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreEditPage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreEditPage.xml index fe2bfcab39ae4..6b020a1bffd95 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreEditPage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreEditPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreEditPage" url="system_store/editStore" module="Magento_Store" area="admin"> <section name="AdminNewStoreViewMainActionsSection"/> <section name="AdminNewStoreSection"/> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupEditPage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupEditPage.xml index 634ee6d651af1..386869a8fa19b 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupEditPage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupEditPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreGroupEditPage" url="admin/system_store/editGroup" area="admin" module="Magento_Store"> <section name="AdminStoreGroupActionsSection"/> </page> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupPage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupPage.xml index 3c73d019aa540..8d48f3fd80417 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupPage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreGroupPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreGroupPage" url="admin/system_store/newGroup" module="Magento_Store" area="admin"> <section name="AdminNewStoreGroupSection"/> </page> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStorePage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStorePage.xml index 9eed4f6557a59..c309c47035bba 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStorePage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStorePage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStorePage" url="/admin/system_store/" area="admin" module="Magento_Store"> <section name="AdminStoresMainActionsSection"/> <section name="AdminStoresGridSection"/> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreViewPage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreViewPage.xml index 15ed31c19f996..4a7de70fb3c35 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreViewPage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreViewPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreViewPage" url="admin/system_store/newStore" module="Magento_Store" area="admin"> <section name="AdminNewStoreViewMainActionsSection"/> <section name="AdminNewStoreSection"/> diff --git a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreWebsitePage.xml b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreWebsitePage.xml index 6f99e4340a070..9296d2667b93d 100644 --- a/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreWebsitePage.xml +++ b/app/code/Magento/Store/Test/Mftf/Page/AdminSystemStoreWebsitePage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminSystemStoreWebsitePage" url="admin/system_store/newWebsite" module="Magento_Store" area="admin"> <section name="AdminNewWebsiteSection"/> </page> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml index 0927a1ffc950b..fda182246db4a 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMainActionsSection"> <element name="storeSwitcher" type="text" selector=".store-switcher"/> <element name="storeViewDropdown" type="button" selector="#store-change-button"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupActionsSection.xml new file mode 100644 index 0000000000000..66e1407f17b26 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupActionsSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminNewStoreGroupActionsSection"> + <element name="backButton" type="button" selector="#back" timeout="30"/> + <element name="delete" type="button" selector="#delete" timeout="30"/> + <element name="resetButton" type="button" selector="#reset" timeout="30"/> + <element name="saveButton" type="button" selector="#save" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml index 106a0f4de5e8b..ea5d9aab8b26d 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewStoreGroupSection"> <element name="storeGrpWebsiteDropdown" type="select" selector="#group_website_id"/> <element name="storeGrpNameTextField" type="input" selector="#group_name"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreSection.xml index cec7a1f4f81e1..5a7d9bba7ebec 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewStoreSection"> <element name="storeNameTextField" type="input" selector="#store_name"/> <element name="storeCodeTextField" type="input" selector="#store_code"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreViewActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreViewActionsSection.xml index 5f8e27f3ec101..faffc69dc6975 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreViewActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreViewActionsSection.xml @@ -5,11 +5,11 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewStoreViewActionsSection"> <element name="backButton" type="button" selector="#back" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> <element name="resetButton" type="button" selector="#reset" timeout="30"/> - <element name="saveButton" type="button" selector="#save" timeout="30"/> + <element name="saveButton" type="button" selector="#save" timeout="60"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteActionsSection.xml index 8048ded87e260..89c0ad15e7dab 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteActionsSection.xml @@ -5,8 +5,8 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewWebsiteActionsSection"> - <element name="saveWebsite" type="button" selector="#save" timeout="30"/> + <element name="saveWebsite" type="button" selector="#save" timeout="60"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteSection.xml index 21dee5f6b6e0d..ea67cf71ccd68 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewWebsiteSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewWebsiteSection"> <element name="name" type="input" selector="#website_name"/> <element name="code" type="input" selector="#website_code"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreBackupOptionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreBackupOptionsSection.xml index 58248b1943714..82ec219f541a3 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreBackupOptionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreBackupOptionsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoreBackupOptionsSection"> <element name="createBackupSelect" type="select" selector="select#store_create_backup"/> </section> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml index ae0283865b774..f79ea080ed1ca 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoreGroupActionsSection.xml @@ -5,8 +5,8 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoreGroupActionsSection"> - <element name="saveButton" type="button" selector="#save" timeout="30" /> + <element name="saveButton" type="button" selector="#save" timeout="60" /> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteStoreGroupSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteStoreGroupSection.xml index ba3d9660b44b3..8ac48dae364b1 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteStoreGroupSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteStoreGroupSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoresDeleteStoreGroupSection"> <element name="createDbBackup" type="select" selector="#store_create_backup"/> <element name="deleteStoreGroupButton" type="button" selector="#delete" timeout="30"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml new file mode 100644 index 0000000000000..fea7dc07c8287 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminStoresDeleteWebsiteSection"> + <element name="createDbBackup" type="select" selector="#store_create_backup"/> + <element name="deleteButton" type="button" selector="#delete" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml index 7630f316d8095..04cbeb5bc596e 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoresGridControlsSection"> <element name="createStoreView" type="button" selector="#add_store"/> <element name="createStore" type="button" selector="#add_group"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml index d86a68d0f7b99..98ad1db46732b 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminStoresMainActionsSection"> <element name="createStoreViewButton" type="button" selector="#add_store" timeout="30"/> <element name="createStoreButton" type="button" selector="#add_group" timeout="30"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml index 4bb62a5a7f6b9..af18e858e1057 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -6,7 +6,7 @@ */ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontHeaderSection"> <element name="storeViewSwitcher" type="button" selector="#switcher-language-trigger"/> <element name="storeViewDropdown" type="button" selector="ul.switcher-dropdown"/> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml index e3345c1f2f094..25e93f8f6ff4c 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml @@ -6,7 +6,7 @@ */ --> <!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateStoreGroupTest"> <annotations> <features value="Store"/> @@ -14,6 +14,7 @@ <title value="Admin should be able to create a store group"/> <description value="Admin should be able to create a store group"/> <group value="store"/> + <severity value="AVERAGE"/> </annotations> <before> <createData stepKey="b1" entity="customStoreGroup"/> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml index d38d4dc992a2b..54d392d0c06f1 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml @@ -6,7 +6,7 @@ */ --> <!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateStoreViewTest"> <annotations> <features value="Store"/> @@ -14,6 +14,7 @@ <title value="Admin should be able to create a store view"/> <description value="Admin should be able to create a store view"/> <group value="storeView"/> + <severity value="AVERAGE"/> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> diff --git a/app/code/Magento/Store/Test/Mftf/composer.json b/app/code/Magento/Store/Test/Mftf/composer.json deleted file mode 100644 index 81305cf049e6c..0000000000000 --- a/app/code/Magento/Store/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-store", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-deploy": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php index 8b4799d2b3437..aca3525a4400e 100644 --- a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php +++ b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php @@ -25,6 +25,12 @@ class SwitcherTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $urlBuilder; + /** @var \Magento\Store\Api\Data\StoreInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $store; + + /** + * @return void + */ protected function setUp() { $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)->getMock(); @@ -33,6 +39,9 @@ protected function setUp() $this->context->expects($this->any())->method('getStoreManager')->will($this->returnValue($this->storeManager)); $this->context->expects($this->any())->method('getUrlBuilder')->will($this->returnValue($this->urlBuilder)); $this->corePostDataHelper = $this->createMock(\Magento\Framework\Data\Helper\PostHelper::class); + $this->store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); $this->switcher = (new ObjectManager($this))->getObject( \Magento\Store\Block\Switcher::class, [ @@ -42,6 +51,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testGetTargetStorePostData() { $store = $this->getMockBuilder(\Magento\Store\Model\Store::class) @@ -50,20 +62,30 @@ public function testGetTargetStorePostData() $store->expects($this->any()) ->method('getCode') ->willReturn('new-store'); - $storeSwitchUrl = 'http://domain.com/stores/store/switch'; + $storeSwitchUrl = 'http://domain.com/stores/store/redirect'; $store->expects($this->atLeastOnce()) ->method('getCurrentUrl') - ->with(true) + ->with(false) + ->willReturn($storeSwitchUrl); + $this->storeManager->expects($this->once()) + ->method('getStore') + ->willReturn($this->store); + $this->store->expects($this->once()) + ->method('getCode') + ->willReturn('old-store'); + $this->urlBuilder->expects($this->once()) + ->method('getUrl') ->willReturn($storeSwitchUrl); $this->corePostDataHelper->expects($this->any()) ->method('getPostData') - ->with($storeSwitchUrl, ['___store' => 'new-store']); + ->with($storeSwitchUrl, ['___store' => 'new-store', 'uenc' => null, '___from_store' => 'old-store']); $this->switcher->getTargetStorePostData($store); } /** * @dataProvider isStoreInUrlDataProvider + * @param bool $isUseStoreInUrl */ public function testIsStoreInUrl($isUseStoreInUrl) { diff --git a/app/code/Magento/Store/Test/Unit/Controller/Store/SwitchActionTest.php b/app/code/Magento/Store/Test/Unit/Controller/Store/SwitchActionTest.php index 1e7b2691a0084..0d337b91c192a 100644 --- a/app/code/Magento/Store/Test/Unit/Controller/Store/SwitchActionTest.php +++ b/app/code/Magento/Store/Test/Unit/Controller/Store/SwitchActionTest.php @@ -8,13 +8,15 @@ use Magento\Framework\App\Http\Context as HttpContext; use Magento\Store\Api\StoreCookieManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Store\Model\Store; -use Magento\Store\Model\StoreResolver; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\StoreResolver; +use Magento\Store\Model\StoreSwitcher; +use Magento\Store\Model\StoreSwitcherInterface; /** * Test class for \Magento\Store\Controller\Store\SwitchAction + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SwitchActionTest extends \PHPUnit\Framework\TestCase { @@ -58,6 +60,12 @@ class SwitchActionTest extends \PHPUnit\Framework\TestCase */ private $redirectMock; + /** @var StoreSwitcherInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $storeSwitcher; + + /** + * @return void + */ protected function setUp() { $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)->getMock(); @@ -73,6 +81,10 @@ protected function setUp() ->getMockForAbstractClass(); $this->redirectMock = $this->getMockBuilder(\Magento\Framework\App\Response\RedirectInterface::class)->getMock(); + $this->storeSwitcher = $this->getMockBuilder(StoreSwitcher::class) + ->disableOriginalConstructor() + ->setMethods(['switch']) + ->getMock(); $this->model = (new ObjectManager($this))->getObject( \Magento\Store\Controller\Store\SwitchAction::class, @@ -83,81 +95,48 @@ protected function setUp() 'storeManager' => $this->storeManagerMock, '_request' => $this->requestMock, '_response' => $this->responseMock, - '_redirect' => $this->redirectMock + '_redirect' => $this->redirectMock, + 'storeSwitcher' => $this->storeSwitcher ] ); } - public function testExecuteSuccessWithoutUseStoreInUrl() + /** + * @return void + */ + public function testExecute() { $storeToSwitchToCode = 'sv2'; $defaultStoreViewCode = 'default'; $expectedRedirectUrl = "magento.com/{$storeToSwitchToCode}"; - $currentActiveStoreMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMock(); $defaultStoreViewMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMock(); $storeToSwitchToMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) ->disableOriginalConstructor() ->setMethods(['isUseStoreInUrl']) ->getMockForAbstractClass(); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($currentActiveStoreMock); - $this->requestMock->expects($this->once())->method('getParam')->willReturn($storeToSwitchToCode); + $this->requestMock->expects($this->any())->method('getParam')->willReturnMap( + [ + [StoreResolver::PARAM_NAME, null, $storeToSwitchToCode], + ['___from_store', null, $defaultStoreViewCode] + ] + ); $this->storeRepositoryMock ->expects($this->once()) - ->method('getActiveStoreByCode') - ->willReturn($storeToSwitchToMock); - $this->storeManagerMock - ->expects($this->once()) - ->method('getDefaultStoreView') + ->method('get') + ->with($defaultStoreViewCode) ->willReturn($defaultStoreViewMock); - $defaultStoreViewMock->expects($this->once())->method('getId')->willReturn($defaultStoreViewCode); - $storeToSwitchToMock->expects($this->once())->method('getId')->willReturn($storeToSwitchToCode); - $storeToSwitchToMock->expects($this->once())->method('isUseStoreInUrl')->willReturn(false); - $this->redirectMock->expects($this->once())->method('getRedirectUrl')->willReturn($expectedRedirectUrl); - $this->responseMock->expects($this->once())->method('setRedirect')->with($expectedRedirectUrl); - - $this->model->execute(); - } - - public function testExecuteSuccessWithUseStoreInUrl() - { - $currentActiveStoreCode = 'sv1'; - $storeToSwitchToCode = 'sv2'; - $defaultStoreViewCode = 'default'; - $originalRedirectUrl = "magento.com/{$currentActiveStoreCode}/test-page/test-sub-page"; - $expectedRedirectUrl = "magento.com/{$storeToSwitchToCode}/test-page/test-sub-page"; - $currentActiveStoreMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->setMethods(['isUseStoreInUrl', 'getBaseUrl']) - ->getMockForAbstractClass(); - $defaultStoreViewMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMock(); - $storeToSwitchToMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->setMethods(['isUseStoreInUrl', 'getBaseUrl']) - ->getMockForAbstractClass(); - - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($currentActiveStoreMock); - $this->requestMock->expects($this->once())->method('getParam')->willReturn($storeToSwitchToCode); $this->storeRepositoryMock ->expects($this->once()) ->method('getActiveStoreByCode') + ->with($storeToSwitchToCode) ->willReturn($storeToSwitchToMock); - $this->storeManagerMock - ->expects($this->once()) - ->method('getDefaultStoreView') - ->willReturn($defaultStoreViewMock); - $defaultStoreViewMock->expects($this->once())->method('getId')->willReturn($defaultStoreViewCode); - $storeToSwitchToMock->expects($this->once())->method('getId')->willReturn($storeToSwitchToCode); - $storeToSwitchToMock->expects($this->once())->method('isUseStoreInUrl')->willReturn(true); - $this->redirectMock->expects($this->any())->method('getRedirectUrl')->willReturn($originalRedirectUrl); - $currentActiveStoreMock - ->expects($this->any()) - ->method('getBaseUrl') - ->willReturn("magento.com/{$currentActiveStoreCode}"); - $storeToSwitchToMock - ->expects($this->once()) - ->method('getBaseUrl') - ->willReturn("magento.com/{$storeToSwitchToCode}"); + $this->storeSwitcher->expects($this->once()) + ->method('switch') + ->with($defaultStoreViewMock, $storeToSwitchToMock, $expectedRedirectUrl) + ->willReturn($expectedRedirectUrl); + + $this->redirectMock->expects($this->once())->method('getRedirectUrl')->willReturn($expectedRedirectUrl); $this->responseMock->expects($this->once())->method('setRedirect')->with($expectedRedirectUrl); $this->model->execute(); diff --git a/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php index e56b5c7fcaa19..0454c0096a6ab 100644 --- a/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/Plugin/StoreCookieTest.php @@ -110,6 +110,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testBeforeDispatchNoSuchEntity() { $storeCode = 'store'; @@ -125,14 +128,13 @@ public function testBeforeDispatchNoSuchEntity() $this->storeCookieManagerMock->expects($this->once()) ->method('deleteStoreCookie') ->with($this->storeMock); - $this->requestMock->expects($this->atLeastOnce()) - ->method('getParam') - ->with(StoreResolverInterface::PARAM_NAME) - ->willReturn(null); $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } + /** + * @return void + */ public function testBeforeDispatchStoreIsInactive() { $storeCode = 'store'; @@ -148,14 +150,13 @@ public function testBeforeDispatchStoreIsInactive() $this->storeCookieManagerMock->expects($this->once()) ->method('deleteStoreCookie') ->with($this->storeMock); - $this->requestMock->expects($this->atLeastOnce()) - ->method('getParam') - ->with(StoreResolverInterface::PARAM_NAME) - ->willReturn(null); $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } + /** + * @return void + */ public function testBeforeDispatchInvalidArgument() { $storeCode = 'store'; @@ -171,14 +172,13 @@ public function testBeforeDispatchInvalidArgument() $this->storeCookieManagerMock->expects($this->once()) ->method('deleteStoreCookie') ->with($this->storeMock); - $this->requestMock->expects($this->atLeastOnce()) - ->method('getParam') - ->with(StoreResolverInterface::PARAM_NAME) - ->willReturn(null); $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } + /** + * @return void + */ public function testBeforeDispatchNoStoreCookie() { $storeCode = null; @@ -194,21 +194,12 @@ public function testBeforeDispatchNoStoreCookie() ->method('deleteStoreCookie') ->with($this->storeMock); - $this->storeResolverMock->expects($this->atLeastOnce()) - ->method('getCurrentStoreId') - ->willReturn(1); - - $this->storeRepositoryMock->expects($this->atLeastOnce()) - ->method('getActiveStoreById') - ->willReturn($this->storeMock); - - $this->storeCookieManagerMock->expects($this->atLeastOnce()) - ->method('setStoreCookie') - ->with($this->storeMock); - $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } + /** + * @return void + */ public function testBeforeDispatchWithStoreRequestParam() { $storeCode = 'store'; @@ -222,23 +213,6 @@ public function testBeforeDispatchWithStoreRequestParam() ->method('deleteStoreCookie') ->with($this->storeMock); - $this->requestMock->expects($this->atLeastOnce()) - ->method('getParam') - ->with(StoreResolverInterface::PARAM_NAME) - ->willReturn($storeCode); - - $this->storeResolverMock->expects($this->atLeastOnce()) - ->method('getCurrentStoreId') - ->willReturn(1); - - $this->storeRepositoryMock->expects($this->atLeastOnce()) - ->method('getActiveStoreById') - ->willReturn($this->storeMock); - - $this->storeCookieManagerMock->expects($this->atLeastOnce()) - ->method('setStoreCookie') - ->with($this->storeMock); - $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); } } diff --git a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php index 4cbf78d0d6471..f98cf5d892e07 100644 --- a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php @@ -43,6 +43,9 @@ class StoreTest extends \PHPUnit\Framework\TestCase */ private $urlModifierMock; + /** + * @return void + */ protected function setUp() { $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -105,6 +108,9 @@ public function loadDataProvider() ]; } + /** + * @return void + */ public function testSetWebsite() { $website = $this->createPartialMock(\Magento\Store\Model\Website::class, ['getId', '__wakeup']); @@ -115,6 +121,9 @@ public function testSetWebsite() $this->assertEquals(2, $model->getWebsiteId()); } + /** + * @return void + */ public function testGetWebsite() { $websiteId = 2; @@ -138,6 +147,9 @@ public function testGetWebsite() $this->assertEquals($website, $model->getWebsite()); } + /** + * @return void + */ public function testGetWebsiteIfWebsiteIsNotExist() { $websiteRepository = $this->getMockBuilder(\Magento\Store\Api\WebsiteRepositoryInterface::class) @@ -156,6 +168,9 @@ public function testGetWebsiteIfWebsiteIsNotExist() $this->assertFalse($model->getWebsite()); } + /** + * @return void + */ public function testGetGroup() { $groupId = 2; @@ -179,6 +194,9 @@ public function testGetGroup() $this->assertEquals($group, $model->getGroup()); } + /** + * @return void + */ public function testGetGroupIfGroupIsNotExist() { $groupRepository = $this->getMockBuilder(\Magento\Store\Api\GroupRepositoryInterface::class) @@ -197,6 +215,9 @@ public function testGetGroupIfGroupIsNotExist() $this->assertFalse($model->getGroup()); } + /** + * @return void + */ public function testGetUrl() { $params = ['_scope_to_url' => true]; @@ -325,6 +346,9 @@ public function getBaseUrlDataProvider() ]; } + /** + * @return void + */ public function testGetBaseUrlEntryPoint() { $expectedPath = 'web/unsecure/base_link_url'; @@ -377,10 +401,11 @@ public function testGetBaseUrlWrongType() * @param boolean $secure * @param string $url * @param string $expected + * @param bool|string $fromStore */ - public function testGetCurrentUrl($secure, $url, $expected) + public function testGetCurrentUrl($secure, $url, $expected, $fromStore) { - $defaultStore = $this->createPartialMock(\Magento\Store\Model\Store::class, [ + $defaultStore = $this->createPartialMock(Store::class, [ 'getId', 'isCurrentlySecure', '__wakeup' @@ -393,15 +418,31 @@ public function testGetCurrentUrl($secure, $url, $expected) $config = $this->getMockForAbstractClass(\Magento\Framework\App\Config\ReinitableConfigInterface::class); - $this->requestMock->expects($this->atLeastOnce())->method('getRequestString')->will($this->returnValue('')); + $requestString = preg_replace( + '/http(s?)\:\/\/[a-z0-9\-]+\//i', + '', + $url + ); + $this->requestMock + ->expects($this->atLeastOnce()) + ->method('getRequestString') + ->willReturn($requestString); $this->requestMock->expects($this->atLeastOnce())->method('getQueryValue')->will($this->returnValue([ 'SID' => 'sid' ])); $urlMock = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class); - $urlMock->expects($this->atLeastOnce())->method('setScope')->will($this->returnSelf()); - $urlMock->expects($this->any())->method('getUrl') - ->will($this->returnValue($url)); + $urlMock + ->expects($this->atLeastOnce()) + ->method('setScope') + ->will($this->returnSelf()); + $urlMock->expects($this->any()) + ->method('getUrl') + ->will($this->returnValue(str_replace($requestString, '', $url))); + $urlMock + ->expects($this->atLeastOnce()) + ->method('escape') + ->willReturnArgument(0); $storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); $storeManager->expects($this->any()) @@ -416,7 +457,7 @@ public function testGetCurrentUrl($secure, $url, $expected) $model->setStoreId(2); $model->setCode('scope_code'); - $this->assertEquals($expected, $model->getCurrentUrl(false)); + $this->assertEquals($expected, $model->getCurrentUrl($fromStore)); } /** @@ -425,9 +466,31 @@ public function testGetCurrentUrl($secure, $url, $expected) public function getCurrentUrlDataProvider() { return [ - [true, 'http://test/url', 'http://test/url?SID=sid&___store=scope_code'], - [true, 'http://test/url?SID=sid1&___store=scope', 'http://test/url?SID=sid&___store=scope_code'], - [false, 'https://test/url', 'https://test/url?SID=sid&___store=scope_code'] + [ + true, + 'http://test/url', + 'http://test/url?SID=sid&___store=scope_code', + false + ], + [ + true, + 'http://test/url?SID=sid1&___store=scope', + 'http://test/url?SID=sid&___store=scope_code', + false + ], + [ + false, + 'https://test/url', + 'https://test/url?SID=sid&___store=scope_code', + false + ], + [ + true, + 'http://test/u/u.2?___store=scope_code', + 'http://test/u/u.2?' + . '___store=scope_code&SID=sid&___from_store=old-store', + 'old-store' + ] ]; } @@ -487,6 +550,9 @@ public function getBaseCurrencyDataProvider() ]; } + /** + * @return void + */ public function testGetAllowedCurrencies() { $currencyPath = 'cur/ren/cy/path'; @@ -612,11 +678,17 @@ public function testGetBaseStaticDir() $this->assertEquals($expectedResult, $this->store->getBaseStaticDir()); } + /** + * @return void + */ public function testGetScopeType() { $this->assertEquals(ScopeInterface::SCOPE_STORE, $this->store->getScopeType()); } + /** + * @return void + */ public function testGetScopeTypeName() { $this->assertEquals('Store View', $this->store->getScopeTypeName()); diff --git a/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php b/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php index ed9ac7ebe438a..f31acd40a2e69 100644 --- a/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php +++ b/app/code/Magento/Store/Test/Unit/Url/Plugin/RouteParamsResolverTest.php @@ -22,6 +22,11 @@ class RouteParamsResolverTest extends \PHPUnit\Framework\TestCase */ protected $queryParamsResolverMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\Store + */ + protected $storeMock; + /** * @var \Magento\Store\Url\Plugin\RouteParamsResolver */ @@ -30,7 +35,19 @@ class RouteParamsResolverTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + + $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->setMethods(['getCode']) + ->disableOriginalConstructor() + ->getMock(); + $this->storeMock->expects($this->any())->method('getCode')->willReturn('custom_store'); + $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManagerMock + ->expects($this->once()) + ->method('getStore') + ->willReturn($this->storeMock); + $this->queryParamsResolverMock = $this->createMock(\Magento\Framework\Url\QueryParamsResolverInterface::class); $this->model = new \Magento\Store\Url\Plugin\RouteParamsResolver( $this->scopeConfigMock, @@ -42,6 +59,8 @@ protected function setUp() public function testBeforeSetRouteParamsScopeInParams() { $storeCode = 'custom_store'; + $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + $this->scopeConfigMock ->expects($this->once()) ->method('getValue') @@ -52,7 +71,7 @@ public function testBeforeSetRouteParamsScopeInParams() ) ->will($this->returnValue(false)); $this->storeManagerMock->expects($this->any())->method('hasSingleStore')->willReturn(false); - $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + /** @var \PHPUnit_Framework_MockObject_MockObject $routeParamsResolverMock */ $routeParamsResolverMock = $this->getMockBuilder(\Magento\Framework\Url\RouteParamsResolver::class) ->setMethods(['setScope', 'getScope']) @@ -61,7 +80,7 @@ public function testBeforeSetRouteParamsScopeInParams() $routeParamsResolverMock->expects($this->once())->method('setScope')->with($storeCode); $routeParamsResolverMock->expects($this->once())->method('getScope')->willReturn($storeCode); - $this->queryParamsResolverMock->expects($this->once())->method('setQueryParam')->with('___store', $storeCode); + $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam'); $this->model->beforeSetRouteParams( $routeParamsResolverMock, @@ -72,6 +91,8 @@ public function testBeforeSetRouteParamsScopeInParams() public function testBeforeSetRouteParamsScopeUseStoreInUrl() { $storeCode = 'custom_store'; + $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + $this->scopeConfigMock ->expects($this->once()) ->method('getValue') @@ -81,8 +102,9 @@ public function testBeforeSetRouteParamsScopeUseStoreInUrl() $storeCode ) ->will($this->returnValue(true)); + $this->storeManagerMock->expects($this->any())->method('hasSingleStore')->willReturn(false); - $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + /** @var \PHPUnit_Framework_MockObject_MockObject $routeParamsResolverMock */ $routeParamsResolverMock = $this->getMockBuilder(\Magento\Framework\Url\RouteParamsResolver::class) ->setMethods(['setScope', 'getScope']) @@ -91,7 +113,7 @@ public function testBeforeSetRouteParamsScopeUseStoreInUrl() $routeParamsResolverMock->expects($this->once())->method('setScope')->with($storeCode); $routeParamsResolverMock->expects($this->once())->method('getScope')->willReturn($storeCode); - $this->queryParamsResolverMock->expects($this->never())->method('setQueryParam'); + $this->queryParamsResolverMock->expects($this->once())->method('setQueryParam')->with('___store', $storeCode); $this->model->beforeSetRouteParams( $routeParamsResolverMock, @@ -102,6 +124,8 @@ public function testBeforeSetRouteParamsScopeUseStoreInUrl() public function testBeforeSetRouteParamsSingleStore() { $storeCode = 'custom_store'; + $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + $this->scopeConfigMock ->expects($this->once()) ->method('getValue') @@ -112,7 +136,7 @@ public function testBeforeSetRouteParamsSingleStore() ) ->will($this->returnValue(false)); $this->storeManagerMock->expects($this->any())->method('hasSingleStore')->willReturn(true); - $data = ['_scope' => $storeCode, '_scope_to_url' => true]; + /** @var \PHPUnit_Framework_MockObject_MockObject $routeParamsResolverMock */ $routeParamsResolverMock = $this->getMockBuilder(\Magento\Framework\Url\RouteParamsResolver::class) ->setMethods(['setScope', 'getScope']) @@ -132,6 +156,8 @@ public function testBeforeSetRouteParamsSingleStore() public function testBeforeSetRouteParamsNoScopeInParams() { $storeCode = 'custom_store'; + $data = ['_scope_to_url' => true]; + $this->scopeConfigMock ->expects($this->once()) ->method('getValue') @@ -140,17 +166,10 @@ public function testBeforeSetRouteParamsNoScopeInParams() \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeCode ) - ->will($this->returnValue(false)); + ->will($this->returnValue(true)); + $this->storeManagerMock->expects($this->any())->method('hasSingleStore')->willReturn(false); - /** @var \PHPUnit_Framework_MockObject_MockObject| $routeParamsResolverMock */ - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->setMethods(['getCode']) - ->disableOriginalConstructor() - ->getMock(); - $storeMock->expects($this->any())->method('getCode')->willReturn($storeCode); - $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $data = ['_scope_to_url' => true]; /** @var \PHPUnit_Framework_MockObject_MockObject $routeParamsResolverMock */ $routeParamsResolverMock = $this->getMockBuilder(\Magento\Framework\Url\RouteParamsResolver::class) ->setMethods(['setScope', 'getScope']) diff --git a/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php b/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php index c4f8a31430963..468352af78cbc 100644 --- a/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php +++ b/app/code/Magento/Store/Url/Plugin/RouteParamsResolver.php @@ -6,6 +6,7 @@ namespace Magento\Store\Url\Plugin; use \Magento\Store\Model\Store; +use \Magento\Store\Api\Data\StoreInterface; use \Magento\Store\Model\ScopeInterface as StoreScopeInterface; /** @@ -51,6 +52,8 @@ public function __construct( * @param \Magento\Framework\Url\RouteParamsResolver $subject * @param array $data * @param bool $unsetOldParams + * @throws \Magento\Framework\Exception\NoSuchEntityException + * * @return array */ public function beforeSetRouteParams( @@ -63,13 +66,19 @@ public function beforeSetRouteParams( unset($data['_scope']); } if (isset($data['_scope_to_url']) && (bool)$data['_scope_to_url'] === true) { - $storeCode = $subject->getScope() ?: $this->storeManager->getStore()->getCode(); + /** @var StoreInterface $currentScope */ + $currentScope = $subject->getScope(); + $storeCode = $currentScope && $currentScope instanceof StoreInterface ? + $currentScope->getCode() : + $this->storeManager->getStore()->getCode(); + $useStoreInUrl = $this->scopeConfig->getValue( Store::XML_PATH_STORE_IN_URL, StoreScopeInterface::SCOPE_STORE, $storeCode ); - if (!$useStoreInUrl && !$this->storeManager->hasSingleStore()) { + + if ($useStoreInUrl && !$this->storeManager->hasSingleStore()) { $this->queryParamsResolver->setQueryParam('___store', $storeCode); } } diff --git a/app/code/Magento/Store/etc/db_schema_whitelist.json b/app/code/Magento/Store/etc/db_schema_whitelist.json index 5fa120333ff12..24ed35c361efb 100644 --- a/app/code/Magento/Store/etc/db_schema_whitelist.json +++ b/app/code/Magento/Store/etc/db_schema_whitelist.json @@ -1,61 +1,61 @@ { - "store_website": { - "column": { - "website_id": true, - "code": true, - "name": true, - "sort_order": true, - "default_group_id": true, - "is_default": true + "store_website": { + "column": { + "website_id": true, + "code": true, + "name": true, + "sort_order": true, + "default_group_id": true, + "is_default": true + }, + "index": { + "STORE_WEBSITE_SORT_ORDER": true, + "STORE_WEBSITE_DEFAULT_GROUP_ID": true + }, + "constraint": { + "PRIMARY": true, + "STORE_WEBSITE_CODE": true + } }, - "index": { - "STORE_WEBSITE_SORT_ORDER": true, - "STORE_WEBSITE_DEFAULT_GROUP_ID": true + "store_group": { + "column": { + "group_id": true, + "website_id": true, + "name": true, + "root_category_id": true, + "default_store_id": true, + "code": true + }, + "index": { + "STORE_GROUP_WEBSITE_ID": true, + "STORE_GROUP_DEFAULT_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "STORE_GROUP_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "STORE_GROUP_CODE": true + } }, - "constraint": { - "PRIMARY": true, - "STORE_WEBSITE_CODE": true + "store": { + "column": { + "store_id": true, + "code": true, + "website_id": true, + "group_id": true, + "name": true, + "sort_order": true, + "is_active": true + }, + "index": { + "STORE_WEBSITE_ID": true, + "STORE_IS_ACTIVE_SORT_ORDER": true, + "STORE_GROUP_ID": true + }, + "constraint": { + "PRIMARY": true, + "STORE_GROUP_ID_STORE_GROUP_GROUP_ID": true, + "STORE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "STORE_CODE": true + } } - }, - "store_group": { - "column": { - "group_id": true, - "website_id": true, - "name": true, - "root_category_id": true, - "default_store_id": true, - "code": true - }, - "index": { - "STORE_GROUP_WEBSITE_ID": true, - "STORE_GROUP_DEFAULT_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "STORE_GROUP_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "STORE_GROUP_CODE": true - } - }, - "store": { - "column": { - "store_id": true, - "code": true, - "website_id": true, - "group_id": true, - "name": true, - "sort_order": true, - "is_active": true - }, - "index": { - "STORE_WEBSITE_ID": true, - "STORE_IS_ACTIVE_SORT_ORDER": true, - "STORE_GROUP_ID": true - }, - "constraint": { - "PRIMARY": true, - "STORE_GROUP_ID_STORE_GROUP_GROUP_ID": true, - "STORE_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "STORE_CODE": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index 27133de270e2f..3d740bfee2093 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -25,6 +25,14 @@ <preference for="Magento\Framework\App\ScopeFallbackResolverInterface" type="Magento\Store\Model\ScopeFallbackResolver"/> <preference for="Magento\Framework\App\ScopeTreeProviderInterface" type="Magento\Store\Model\ScopeTreeProvider"/> <preference for="Magento\Framework\App\ScopeValidatorInterface" type="Magento\Store\Model\ScopeValidator"/> + <preference for="Magento\Store\Model\StoreSwitcherInterface" type="Magento\Store\Model\StoreSwitcher" /> + <type name="Magento\Framework\App\Http\Context"> + <arguments> + <argument name="default" xsi:type="array"> + <item name="website" xsi:type="string">0</item> + </argument> + </arguments> + </type> <type name="Magento\Framework\App\Response\Http"> <plugin name="genericHeaderPlugin" type="Magento\Framework\App\Response\HeaderManager"/> </type> @@ -57,7 +65,7 @@ <preference for="Magento\Framework\App\Router\PathConfigInterface" type="Magento\Store\Model\PathConfig" /> <type name="Magento\Framework\App\Action\AbstractAction"> <plugin name="storeCheck" type="Magento\Store\App\Action\Plugin\StoreCheck" sortOrder="10"/> - <plugin name="designLoader" type="Magento\Framework\App\Action\Plugin\Design" sortOrder="30"/> + <plugin name="designLoader" type="Magento\Framework\App\Action\Plugin\Design" /> </type> <type name="Magento\Framework\Url\SecurityInfo"> <plugin name="storeUrlSecurityInfo" type="Magento\Store\Url\Plugin\SecurityInfo"/> @@ -417,4 +425,13 @@ </argument> </arguments> </type> + <type name="Magento\Store\Model\StoreSwitcher"> + <arguments> + <argument name="storeSwitchers" xsi:type="array"> + <item name="cleanTargetUrl" xsi:type="object">Magento\Store\Model\StoreSwitcher\CleanTargetUrl</item> + <item name="manageStoreCookie" xsi:type="object">Magento\Store\Model\StoreSwitcher\ManageStoreCookie</item> + <item name="managePrivateContent" xsi:type="object">Magento\Store\Model\StoreSwitcher\ManagePrivateContent</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php new file mode 100644 index 0000000000000..0252d7898beee --- /dev/null +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/Store/StoreConfigDataProvider.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\StoreGraphQl\Model\Resolver\Store; + +use Magento\Store\Api\Data\StoreConfigInterface; +use Magento\Store\Api\StoreConfigManagerInterface; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Api\StoreResolverInterface; + +/** + * StoreConfig field data provider, used for GraphQL request processing. + */ +class StoreConfigDataProvider +{ + /** + * @var StoreConfigManagerInterface + */ + private $storeConfigManager; + + /** + * @var StoreResolverInterface + */ + private $storeResolver; + + /** + * @var StoreRepositoryInterface + */ + private $storeRepository; + + /** + * @param StoreConfigManagerInterface $storeConfigManager + * @param StoreResolverInterface $storeResolver + * @param StoreRepositoryInterface $storeRepository + */ + public function __construct( + StoreConfigManagerInterface $storeConfigManager, + StoreResolverInterface $storeResolver, + StoreRepositoryInterface $storeRepository + ) { + $this->storeConfigManager = $storeConfigManager; + $this->storeResolver = $storeResolver; + $this->storeRepository = $storeRepository; + } + + /** + * Get store config for current store + * + * @return array + */ + public function getStoreConfig() : array + { + $storeId = $this->storeResolver->getCurrentStoreId(); + $store = $this->storeRepository->getById($storeId); + $storeConfig = current($this->storeConfigManager->getStoreConfigs([$store->getCode()])); + + return $this->hidrateStoreConfig($storeConfig); + } + + /** + * Transform StoreConfig object to in array format + * + * @param StoreConfigInterface $storeConfig + * @return array + */ + private function hidrateStoreConfig($storeConfig): array + { + /** @var StoreConfigInterface $storeConfig */ + $storeConfigData = [ + 'id' => $storeConfig->getId(), + 'code' => $storeConfig->getCode(), + 'website_id' => $storeConfig->getWebsiteId(), + 'locale' => $storeConfig->getLocale(), + 'base_currency_code' => $storeConfig->getBaseCurrencyCode(), + 'default_display_currency_code' => $storeConfig->getDefaultDisplayCurrencyCode(), + 'timezone' => $storeConfig->getTimezone(), + 'weight_unit' => $storeConfig->getWeightUnit(), + 'base_url' => $storeConfig->getBaseUrl(), + 'base_link_url' => $storeConfig->getBaseLinkUrl(), + 'base_static_url' => $storeConfig->getSecureBaseStaticUrl(), + 'base_media_url' => $storeConfig->getBaseMediaUrl(), + 'secure_base_url' => $storeConfig->getSecureBaseUrl(), + 'secure_base_link_url' => $storeConfig->getSecureBaseLinkUrl(), + 'secure_base_static_url' => $storeConfig->getSecureBaseStaticUrl(), + 'secure_base_media_url' => $storeConfig->getSecureBaseMediaUrl() + ]; + + return $storeConfigData; + } +} diff --git a/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php new file mode 100644 index 0000000000000..8e5bf0120b8b8 --- /dev/null +++ b/app/code/Magento/StoreGraphQl/Model/Resolver/StoreConfigResolver.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\StoreGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\Resolver\Value; +use Magento\Framework\GraphQl\Query\Resolver\ValueFactory; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider; + +/** + * StoreConfig page field resolver, used for GraphQL request processing. + */ +class StoreConfigResolver implements ResolverInterface +{ + /** + * @var StoreConfigDataProvider + */ + private $storeConfigDataProvider; + + /** + * @var ValueFactory + */ + private $valueFactory; + + /** + * @param StoreConfigDataProvider $storeConfigsDataProvider + * @param ValueFactory $valueFactory + */ + public function __construct( + StoreConfigDataProvider $storeConfigsDataProvider, + ValueFactory $valueFactory + ) { + $this->valueFactory = $valueFactory; + $this->storeConfigDataProvider = $storeConfigsDataProvider; + } + + /** + * {@inheritdoc} + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) : Value { + + $storeConfigData = $this->storeConfigDataProvider->getStoreConfig(); + + $result = function () use ($storeConfigData) { + return !empty($storeConfigData) ? $storeConfigData : []; + }; + + return $this->valueFactory->create($result); + } +} diff --git a/app/code/Magento/StoreGraphQl/Test/Mftf/composer.json b/app/code/Magento/StoreGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 10407d0dc179c..0000000000000 --- a/app/code/Magento/StoreGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-store-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-graph-ql": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/StoreGraphQl/composer.json b/app/code/Magento/StoreGraphQl/composer.json index 91f79b39c023a..d03d759babd22 100644 --- a/app/code/Magento/StoreGraphQl/composer.json +++ b/app/code/Magento/StoreGraphQl/composer.json @@ -4,7 +4,8 @@ "type": "magento2-module", "require": { "php": "~7.1.3||~7.2.0", - "magento/framework": "*" + "magento/framework": "*", + "magento/module-store": "*" }, "suggest": { "magento/module-graph-ql": "*", diff --git a/app/code/Magento/StoreGraphQl/etc/schema.graphqls b/app/code/Magento/StoreGraphQl/etc/schema.graphqls index 6eea6da8fd6fa..af79d0e3e28b7 100644 --- a/app/code/Magento/StoreGraphQl/etc/schema.graphqls +++ b/app/code/Magento/StoreGraphQl/etc/schema.graphqls @@ -1,5 +1,8 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type Query { + storeConfig : StoreConfig @resolver(class: "Magento\\StoreGraphQl\\Model\\Resolver\\StoreConfigResolver") @doc(description: "The store config query") +} type Website @doc(description: "The type contains information about a website") { id : Int @doc(description: "The ID number assigned to the website") @@ -9,3 +12,22 @@ type Website @doc(description: "The type contains information about a website") default_group_id : String @doc(description: "The default group id that the website has") is_default : Boolean @doc(description: "Specifies if this is the default website") } + +type StoreConfig @doc(description: "The type contains information about a store config") { + id : Int @doc(description: "The ID number assigned to the store") + code : String @doc(description: "A code assigned to the store to identify it") + website_id : Int @doc(description: "The ID number assigned to the website store belongs") + locale : String @doc(description: "Store locale") + base_currency_code : String @doc(description: "Base currency code") + default_display_currency_code : String @doc(description: "Default display currency code") + timezone : String @doc(description: "Timezone of the store") + weight_unit : String @doc(description: "The unit of weight") + base_url : String @doc(description: "Base URL for the store") + base_link_url : String @doc(description: "Base link URL for the store") + base_static_url : String @doc(description: "Base static URL for the store") + base_media_url : String @doc(description: "Base media URL for the store") + secure_base_url : String @doc(description: "Secure base URL for the store") + secure_base_link_url : String @doc(description: "Secure base link URL for the store") + secure_base_static_url : String @doc(description: "Secure base static URL for the store") + secure_base_media_url : String @doc(description: "Secure base media URL for the store") +} diff --git a/app/code/Magento/Swagger/Test/Mftf/composer.json b/app/code/Magento/Swagger/Test/Mftf/composer.json deleted file mode 100644 index a997257192fce..0000000000000 --- a/app/code/Magento/Swagger/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-swagger", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml index f14df1c70a790..5a592b9b7c987 100644 --- a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml +++ b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml @@ -15,8 +15,8 @@ <link src='Magento_Swagger::swagger-ui/js/lang/translator.js' type='text/javascript' defer="defer"/> <link src='Magento_Swagger::swagger-ui/js/lang/ru.js' type='text/javascript' defer="defer"/> <link src='Magento_Swagger::swagger-ui/js/lang/en.js' type='text/javascript' defer="defer"/> - <link src='Magento_Swagger::swagger-ui/js/swagger-ui-bundle.js' type='text/javascript' defer="defer"/> - <link src='Magento_Swagger::swagger-ui/js/swagger-ui-standalone-preset.js' type='text/javascript' defer="defer"/> + <link src='Magento_Swagger::swagger-ui/js/swagger-ui-bundle.min.js' type='text/javascript' defer="defer"/> + <link src='Magento_Swagger::swagger-ui/js/swagger-ui-standalone-preset.min.js' type='text/javascript' defer="defer"/> <link src='Magento_Swagger::swagger-ui/js/magento-swagger.js' type='text/javascript' defer="defer"/> <!--Remove require-js assets--> diff --git a/app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-bundle.js b/app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-bundle.min.js similarity index 100% rename from app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-bundle.js rename to app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-bundle.min.js diff --git a/app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-standalone-preset.js b/app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-standalone-preset.min.js similarity index 100% rename from app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-standalone-preset.js rename to app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-standalone-preset.min.js diff --git a/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json b/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json deleted file mode 100644 index 22ed346ef2081..0000000000000 --- a/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-swagger-webapi", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-swagger": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SwaggerWebapiAsync/Test/Mftf/composer.json b/app/code/Magento/SwaggerWebapiAsync/Test/Mftf/composer.json deleted file mode 100644 index fe20e7a539d03..0000000000000 --- a/app/code/Magento/SwaggerWebapiAsync/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-swagger-webapi-async", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-swagger": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/SwaggerWebapiAsync/composer.json b/app/code/Magento/SwaggerWebapiAsync/composer.json index 5ca538636b38a..3735bcd0ee458 100644 --- a/app/code/Magento/SwaggerWebapiAsync/composer.json +++ b/app/code/Magento/SwaggerWebapiAsync/composer.json @@ -14,7 +14,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml new file mode 100644 index 0000000000000..60a8035dedeca --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + + <actionGroup name="AddVisualSwatchToProductActionGroup"> + <arguments> + <argument name="attribute" defaultValue="visualSwatchAttribute"/> + <argument name="option1" defaultValue="visualSwatchOption1"/> + <argument name="option2" defaultValue="visualSwatchOption2"/> + </arguments> + + <seeInCurrentUrl url="{{ProductCatalogPage.url}}" stepKey="seeOnProductEditPage"/> + <conditionalClick selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="openConfigurationPanel"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="waitForSlideOut"/> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="clickCreateNewAttribute"/> + <waitForPageLoad stepKey="waitForIFrame"/> + + <switchToIFrame selector="{{AdminNewAttributePanel.newAttributeIFrame}}" stepKey="switchToNewAttributeIFrame"/> + <fillField selector="{{AdminNewAttributePanel.defaultLabel}}" userInput="{{attribute.default_label}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AdminNewAttributePanel.inputType}}" userInput="{{attribute.input_type}}" stepKey="selectInputType"/> + <!--Add swatch options--> + <click selector="{{AdminNewAttributePanel.addVisualSwatchOption}}" stepKey="clickAddSwatch1"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.visualSwatchOptionAdminValue('0')}}" stepKey="waitForOption1Row"/> + <fillField selector="{{AdminNewAttributePanel.visualSwatchOptionAdminValue('0')}}" userInput="{{option1.admin_label}}" stepKey="fillAdminLabel1"/> + <fillField selector="{{AdminNewAttributePanel.visualSwatchOptionDefaultStoreValue('0')}}" userInput="{{option1.default_label}}" stepKey="fillDefaultStoreLabel1"/> + <click selector="{{AdminNewAttributePanel.addVisualSwatchOption}}" stepKey="clickAddSwatch2"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.visualSwatchOptionAdminValue('1')}}" stepKey="waitForOption2Row"/> + <fillField selector="{{AdminNewAttributePanel.visualSwatchOptionAdminValue('1')}}" userInput="{{option2.admin_label}}" stepKey="fillAdminLabel2"/> + <fillField selector="{{AdminNewAttributePanel.visualSwatchOptionDefaultStoreValue('1')}}" userInput="{{option2.default_label}}" stepKey="fillDefaultStoreLabel2"/> + + <!--Save attribute--> + <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickOnNewAttributePanel"/> + <waitForPageLoad stepKey="waitForSaveAttribute"/> + <switchToIFrame stepKey="switchOutOfIFrame"/> + + <!--Find attribute in grid and select--> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="clickOnFilters"/> + <fillField selector="{{AdminDataGridHeaderSection.attributeCodeFilterInput}}" userInput="{{attribute.default_label}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminDataGridTableSection.rowCheckbox('1')}}" stepKey="clickOnFirstCheckbox"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep1"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(attribute.default_label)}}" stepKey="clickSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep2"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="100" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextStep3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="generateProducts"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSaveProductMessage"/> + </actionGroup> + +</actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/ColorPickerActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/ColorPickerActionGroup.xml new file mode 100644 index 0000000000000..6f991274a015b --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/ColorPickerActionGroup.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="setColorPickerByHex"> + <arguments> + <argument name="nthColorPicker" type="string" defaultValue="1"/> + <argument name="hexColor" type="string" defaultValue="e74c3c"/> + </arguments> + <!-- This 6x backspace stuff is some magic that is necessary to interact with this field correctly --> + <pressKey selector="{{AdminColorPickerSection.hexByIndex(nthColorPicker)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::BACKSPACE,\Facebook\WebDriver\WebDriverKeys::BACKSPACE,\Facebook\WebDriver\WebDriverKeys::BACKSPACE,\Facebook\WebDriver\WebDriverKeys::BACKSPACE,\Facebook\WebDriver\WebDriverKeys::BACKSPACE,\Facebook\WebDriver\WebDriverKeys::BACKSPACE,'{{hexColor}}']" stepKey="fillHex1"/> + <click selector="{{AdminColorPickerSection.submitByIndex(nthColorPicker)}}" stepKey="submitColor1"/> + </actionGroup> + <actionGroup name="assertSwatchColor"> + <arguments> + <argument name="nthSwatch" type="string" defaultValue="1"/> + <argument name="expectedStyle" type="string" defaultValue="background: rgb(0, 0, 0);"/> + </arguments> + <grabAttributeFrom selector="{{AdminManageSwatchSection.nthSwatch(nthSwatch)}}" userInput="style" stepKey="grabStyle1"/> + <assertEquals stepKey="assertStyle1"> + <actualResult type="string">{$grabStyle1}</actualResult> + <expectedResult type="string">{{expectedStyle}}</expectedResult> + </assertEquals> + </actionGroup> + <actionGroup name="assertStorefrontSwatchColor"> + <arguments> + <argument name="nthSwatch" type="string" defaultValue="1"/> + <argument name="expectedRgb" type="string" defaultValue="rgb(231, 77, 60)"/> + </arguments> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOption(nthSwatch)}}" userInput="style" stepKey="grabStyle1"/> + <assertEquals stepKey="assertStyle1"> + <actualResult type="string">{$grabStyle1}</actualResult> + <expectedResult type="string">background: center center no-repeat {{expectedRgb}};</expectedResult> + </assertEquals> + </actionGroup> + <actionGroup name="openSwatchMenuByIndex"> + <arguments> + <argument name="index" type="string" defaultValue="0"/> + </arguments> + <!-- I had to use executeJS to perform the click to get around the use of CSS ::before and ::after --> + <executeJS function="jQuery('#swatch_window_option_option_{{index}}').click()" stepKey="clickSwatch1"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml new file mode 100644 index 0000000000000..08e24cfeb38fe --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="visualSwatchAttribute" type="SwatchAttribute"> + <data key="default_label" unique="suffix">VisualSwatchAttr</data> + <data key="input_type">Visual Swatch</data> + </entity> +</entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchOptionData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchOptionData.xml new file mode 100644 index 0000000000000..4608d1a9190a9 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchOptionData.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="visualSwatchOption1" type="SwatchOption"> + <data key="admin_label" unique="suffix">VisualOpt1</data> + <data key="default_label" unique="suffix">VisualOpt1</data> + </entity> + + <entity name="visualSwatchOption2" type="SwatchOption"> + <data key="admin_label" unique="suffix">VisualOpt2</data> + <data key="default_label" unique="suffix">VisualOpt2</data> + </entity> +</entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/AdminColorPickerSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/AdminColorPickerSection.xml new file mode 100644 index 0000000000000..50d56d64bb67b --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/AdminColorPickerSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminColorPickerSection"> + <element name="hexByIndex" type="input" selector="//div[@class='colorpicker'][{{var}}]/div[@class='colorpicker_hex']/input" parameterized="true"/> + <element name="submitByIndex" type="button" selector="//div[@class='colorpicker'][{{var}}]/div[@class='colorpicker_submit']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml new file mode 100644 index 0000000000000..25e03676f6870 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminManageSwatchSection"> + <element name="adminInputByIndex" type="input" selector="optionvisual[value][option_{{var}}][0]" parameterized="true"/> + <element name="addSwatch" type="button" selector="#add_new_swatch_visual_option_button" timeout="30"/> + <element name="nthSwatch" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_window" parameterized="true"/> + <element name="addSwatchText" type="button" selector="#add_new_swatch_text_option_button"/> + <element name="swatchTextByIndex" type="input" selector="input[name='swatchtext[value][option_{{index}}][0]']" parameterized="true"/> + <element name="nthSwatchText" type="input" selector="#swatch-text-options-panel table tbody tr:nth-of-type({{var}}) td:nth-of-type(3) input" parameterized="true"/> + <element name="nthSwatchAdminDescription" type="input" selector="#swatch-text-options-panel table tbody tr:nth-of-type({{var}}) td:nth-of-type(4) input" parameterized="true"/> + <!-- Selector for Admin Description input where the index is zero-based --> + <element name="swatchAdminDescriptionByIndex" type="input" selector="input[name='optiontext[value][option_{{index}}][0]']" parameterized="true"/> + <element name="nthChooseColor" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.colorpicker_handler" parameterized="true"/> + <element name="nthUploadFile" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.btn_choose_file_upload" parameterized="true"/> + <element name="nthDelete" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) button.delete-option" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/AdminNewAttributePanelSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/AdminNewAttributePanelSection.xml new file mode 100644 index 0000000000000..adefce9182724 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/AdminNewAttributePanelSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminNewAttributePanel"> + <element name="addVisualSwatchOption" type="button" selector="button#add_new_swatch_visual_option_button"/> + <element name="addTextSwatchOption" type="button" selector="button#add_new_swatch_text_option_button"/> + <element name="visualSwatchOptionAdminValue" type="input" selector="[data-role='swatch-visual-options-container'] input[name='optionvisual[value][option_{{row}}][0]']" parameterized="true"/> + <element name="visualSwatchOptionDefaultStoreValue" type="input" selector="[data-role='swatch-visual-options-container'] input[name='optionvisual[value][option_{{row}}][1]']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategorySidebarSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategorySidebarSection.xml new file mode 100644 index 0000000000000..43746fc08a0da --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontCategorySidebarSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCategorySidebarSection"> + <element name="layeredFilterBlock" type="block" selector="#layered-filter-block"/> + <element name="filterOptionTitle" type="button" selector="//div[@class='filter-options-title'][text() = '{{var}}']" parameterized="true" timeout="30"/> + <element name="attributeNthOption" type="button" selector="div.{{attributeLabel}} a:nth-of-type({{n}}) div" parameterized="true" timeout="30"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml new file mode 100644 index 0000000000000..415ae88fceb52 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontProductInfoMainSection"> + <element name="swatchOptionByLabel" type="button" selector="div.swatch-option[option-label={{opt}}]" parameterized="true"/> + <element name="nthSwatchOption" type="button" selector="div.swatch-option:nth-of-type({{var}})" parameterized="true"/> + <element name="selectedSwatchValue" type="text" selector="//div[contains(@class, 'swatch-attribute') and contains(., '{{attr}}')]//span[contains(@class, 'swatch-attribute-selected-option')]" parameterized="true"/> + <element name="swatchAttributeOptions" type="text" selector="div.swatch-attribute-options"/> + <element name="nthSwatchOptionText" type="button" selector="div.swatch-option.text:nth-of-type({{n}})" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml new file mode 100644 index 0000000000000..a763bda2e494f --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateImageSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="Create/configure swatches"/> + <title value="Admin can create product attribute with image swatch"/> + <description value="Admin can create product attribute with image swatch"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3077"/> + <group value="Swatches"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Begin creating a new product attribute of type "Image Swatch" --> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> + + <!-- Select visual swatch --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_visual" stepKey="selectInputType"/> + + <!-- This hack is because the same <input type="file"> is re-purposed used for all uploads. --> + <executeJS function="HTMLInputElement.prototype.click = function() { if(this.type !== 'file') HTMLElement.prototype.click.call(this); };" stepKey="disableClick"/> + + <!-- Set swatch image #1 --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch1"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> + <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + + <!-- Set swatch image #2 --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="1"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('2')}}" stepKey="clickUploadFile2"/> + <attachFile selector="input[name='datafile']" userInput="adobe-small.jpg" stepKey="attachFile2"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="adobe-small" stepKey="fillAdmin2"/> + + <!-- Set swatch image #3 --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch3"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch3"> + <argument name="index" value="2"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('3')}}" stepKey="clickUploadFile3"/> + <attachFile selector="input[name='datafile']" userInput="adobe-base.jpg" stepKey="attachFile3"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('2')}}" userInput="adobe-base" stepKey="fillAdmin3"/> + + <!-- Set scope --> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + + <!-- Save the new product attribute --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Verify after round trip to the server --> + <grabAttributeFrom selector="{{AdminManageSwatchSection.nthSwatch('1')}}" userInput="style" stepKey="grabSwatch1"/> + <assertContains stepKey="assertSwatch1"> + <expectedResult type="string">adobe-thumb</expectedResult> + <actualResult type="string">{$grabSwatch1}</actualResult> + </assertContains> + <grabAttributeFrom selector="{{AdminManageSwatchSection.nthSwatch('2')}}" userInput="style" stepKey="grabSwatch2"/> + <assertContains stepKey="assertSwatch2"> + <expectedResult type="string">adobe-small</expectedResult> + <actualResult type="string">{$grabSwatch2}</actualResult> + </assertContains> + <grabAttributeFrom selector="{{AdminManageSwatchSection.nthSwatch('3')}}" userInput="style" stepKey="grabSwatch3"/> + <assertContains stepKey="assertSwatch3"> + <expectedResult type="string">adobe-base</expectedResult> + <actualResult type="string">{$grabSwatch3}</actualResult> + </assertContains> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad time="30" stepKey="waitForProductGrid"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + + <!-- Create configurations based off the Image Swatch we created earlier --> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="1" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + + <!-- Go to the product page and see text swatch options --> + <amOnPage url="{{BaseConfigurableProduct.sku}}.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!-- Verify the storefront --> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOption('1')}}" userInput="style" stepKey="grabSwatch4"/> + <assertContains stepKey="assertSwatch4"> + <expectedResult type="string">adobe-thumb</expectedResult> + <actualResult type="string">{$grabSwatch4}</actualResult> + </assertContains> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOption('2')}}" userInput="style" stepKey="grabSwatch5"/> + <assertContains stepKey="assertSwatch5"> + <expectedResult type="string">adobe-small</expectedResult> + <actualResult type="string">{$grabSwatch5}</actualResult> + </assertContains> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOption('3')}}" userInput="style" stepKey="grabSwatch6"/> + <assertContains stepKey="assertSwatch6"> + <expectedResult type="string">adobe-base</expectedResult> + <actualResult type="string">{$grabSwatch6}</actualResult> + </assertContains> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml new file mode 100644 index 0000000000000..3ef347b7aca12 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateTextSwatchTest.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateTextSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="Create/configure swatches"/> + <title value="Admin can create product attribute with text swatch"/> + <description value="Admin can create product attribute with text swatch"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3078"/> + <group value="Swatches"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Create a new product attribute of type "Text Swatch" --> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_text" stepKey="selectInputType"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch0"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('0')}}" userInput="red" stepKey="fillSwatch0"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('0')}}" userInput="Something red." stepKey="fillDescription0"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('1')}}" userInput="green" stepKey="fillSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('1')}}" userInput="Something green." stepKey="fillDescription1"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch2"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('2')}}" userInput="blue" stepKey="fillSwatch2"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('2')}}" userInput="Something blue." stepKey="fillDescription2"/> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + <!-- Save and verify --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSave"/> + <seeInField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeDefaultLabel"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchText('1')}}" userInput="red" stepKey="seeSwatch0"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchAdminDescription('1')}}" userInput="Something red." stepKey="seeDescription0"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchText('2')}}" userInput="green" stepKey="seeSwatch1"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchAdminDescription('2')}}" userInput="Something green." stepKey="seeDescription1"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchText('3')}}" userInput="blue" stepKey="seeSwatch2"/> + <seeInField selector="{{AdminManageSwatchSection.nthSwatchAdminDescription('3')}}" userInput="Something blue." stepKey="seeDescription2"/> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad time="30" stepKey="waitForProductGrid"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> + <fillField userInput="{{_defaultProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + + <!-- Create configurations based off the Text Swatch we created earlier --> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="1" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + + <!-- Go to the product page and see text swatch options --> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.swatchAttributeOptions}}" userInput="red" stepKey="seeRed"/> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOptionText('1')}}" userInput="option-label" stepKey="grabRedLabel"/> + <assertEquals stepKey="assertRedLabel"> + <expectedResult type="string">Something red.</expectedResult> + <actualResult type="string">{$grabRedLabel}</actualResult> + </assertEquals> + <see selector="{{StorefrontProductInfoMainSection.swatchAttributeOptions}}" userInput="green" stepKey="seeGreen"/> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOptionText('2')}}" userInput="option-label" stepKey="grabGreenLabel"/> + <assertEquals stepKey="assertGreenLabel"> + <expectedResult type="string">Something green.</expectedResult> + <actualResult type="string">{$grabGreenLabel}</actualResult> + </assertEquals> + <see selector="{{StorefrontProductInfoMainSection.swatchAttributeOptions}}" userInput="blue" stepKey="seeBlue"/> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.nthSwatchOptionText('3')}}" userInput="option-label" stepKey="grabBlueLabel"/> + <assertEquals stepKey="assertBlueLabel"> + <expectedResult type="string">Something blue.</expectedResult> + <actualResult type="string">{$grabBlueLabel}</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml new file mode 100644 index 0000000000000..90e94466351b6 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateVisualSwatchTest.xml @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateVisualSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="Create/configure swatches"/> + <title value="Admin can create product attribute with picked color swatch"/> + <description value="Admin can create product attribute with picked color swatch"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3080"/> + <group value="Swatches"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Clean up our modifications to the existing color attribute --> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <waitForPageLoad stepKey="waitForProductAttributes"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> + <click selector="{{AdminManageSwatchSection.nthDelete('1')}}" stepKey="deleteSwatch1"/> + <click selector="{{AdminManageSwatchSection.nthDelete('2')}}" stepKey="deleteSwatch2"/> + <click selector="{{AdminManageSwatchSection.nthDelete('3')}}" stepKey="deleteSwatch3"/> + <waitForPageLoad stepKey="waitToClickSave"/> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit"/> + <!-- Logout --> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Go to the edit page for the "color" attribute --> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <waitForPageLoad stepKey="waitForProductAttributes"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> + + <!-- Change to visual swatches --> + <selectOption selector="{{AdminNewAttributePanel.inputType}}" userInput="swatch_visual" stepKey="selectVisualSwatch"/> + + <!-- Set swatch #1 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch1"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('1')}}" stepKey="clickChooseColor1"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex1"> + <argument name="nthColorPicker" value="1"/> + <argument name="hexColor" value="e74c3c"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="red" stepKey="fillAdmin1"/> + <!-- Set swatch #2 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="1"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('2')}}" stepKey="clickChooseColor2"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex2"> + <argument name="nthColorPicker" value="2"/> + <argument name="hexColor" value="2ecc71"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="green" stepKey="fillAdmin2"/> + <!-- Set swatch #3 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch3"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch3"> + <argument name="index" value="2"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('3')}}" stepKey="clickChooseColor3"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex3"> + <argument name="nthColorPicker" value="3"/> + <argument name="hexColor" value="3498db"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('2')}}" userInput="blue" stepKey="fillAdmin3"/> + <waitForPageLoad stepKey="waitToClickSave"/> + + <!-- Save --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Assert that the Save was successful after round trip to server --> + <actionGroup ref="assertSwatchColor" stepKey="assertSwatch1"> + <argument name="nthSwatch" value="1"/> + <argument name="expectedStyle" value="background: rgb(231, 77, 60);"/> + </actionGroup> + <actionGroup ref="assertSwatchColor" stepKey="assertSwatch2"> + <argument name="nthSwatch" value="2"/> + <argument name="expectedStyle" value="background: rgb(46, 204, 112);"/> + </actionGroup> + <actionGroup ref="assertSwatchColor" stepKey="assertSwatch3"> + <argument name="nthSwatch" value="3"/> + <argument name="expectedStyle" value="background: rgb(52, 152, 219);"/> + </actionGroup> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnAddProductToggle"/> + <click selector="{{AdminProductGridActionSection.addConfigurableProduct}}" stepKey="clickOnAddConfigurableProduct"/> + <fillField userInput="{{_defaultProduct.name}}" selector="{{AdminProductFormSection.productName}}" stepKey="fillName"/> + <fillField userInput="{{_defaultProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" stepKey="fillSKU"/> + <fillField userInput="{{_defaultProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" stepKey="fillPrice"/> + <fillField userInput="{{_defaultProduct.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" stepKey="fillQuantity"/> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> + <fillField userInput="{{_defaultProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> + + <!-- Create configurations based off the Text Swatch we created earlier --> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="color" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyUniquePricesByAttributeToEachSku}}" stepKey="clickOnApplyUniquePricesByAttributeToEachSku"/> + <selectOption selector="{{AdminCreateProductConfigurationsPanel.selectAttribute}}" userInput="Color" stepKey="selectAttributes"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attribute1}}" userInput="10" stepKey="fillAttributePrice1"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attribute2}}" userInput="20" stepKey="fillAttributePrice2"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attribute3}}" userInput="30" stepKey="fillAttributePrice3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="99" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <!-- conditionalClick is necessary because this popup appears in Jenkins but not locally. I cannot figure out why. --> + <conditionalClick selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" dependentSelector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" visible="true" stepKey="clickOnConfirmInPopup"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> + <seeInTitle userInput="{{_defaultProduct.name}}" stepKey="seeProductNameInTitle"/> + + <!-- Go to the product page and see text swatch options --> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!-- Verify that the storefront shows the swatches too --> + <actionGroup ref="assertStorefrontSwatchColor" stepKey="assertSwatch4"> + <argument name="nthSwatch" value="1"/> + <argument name="expectedRgb" value="rgb(231, 77, 60)"/> + </actionGroup> + <actionGroup ref="assertStorefrontSwatchColor" stepKey="assertSwatch5"> + <argument name="nthSwatch" value="2"/> + <argument name="expectedRgb" value="rgb(46, 204, 112)"/> + </actionGroup> + <actionGroup ref="assertStorefrontSwatchColor" stepKey="assertSwatch6"> + <argument name="nthSwatch" value="3"/> + <argument name="expectedRgb" value="rgb(52, 152, 219)"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml new file mode 100644 index 0000000000000..e4c96ab3a2ba7 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontFilterByImageSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="View swatches in product listing"/> + <title value="Customers can filter products using image swatches"/> + <description value="Customers can filter products using image swatches"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3461"/> + <group value="Swatches"/> + </annotations> + + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Begin creating a new product attribute --> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> + + <!-- Select visual swatch --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_visual" stepKey="selectInputType"/> + + <!-- This hack is because the same <input type="file"> is re-purposed used for all uploads. --> + <executeJS function="HTMLInputElement.prototype.click = function() { if(this.type !== 'file') HTMLElement.prototype.click.call(this); };" stepKey="disableClick"/> + + <!-- Set swatch #1 image using file upload --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch1"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> + <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + + <!-- Set swatch #2 image using the file upload --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="1"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('2')}}" stepKey="clickUploadFile2"/> + <attachFile selector="input[name='datafile']" userInput="adobe-small.jpg" stepKey="attachFile2"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="adobe-small" stepKey="fillAdmin2"/> + + <!-- Set scope to global --> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + + <!-- Set Use In Layered Navigation --> + <scrollToTopOfPage stepKey="scrollToTop1"/> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="goToStorefrontProperties"/> + <selectOption selector="{{AttributePropertiesSection.useInLayeredNavigation}}" userInput="1" stepKey="selectUseInLayeredNavigation"/> + + <!-- Save the new attribute --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad time="30" stepKey="waitForProductGrid"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Create configurations based off the visual swatch we created earlier --> + <actionGroup ref="createConfigurationsForAttribute" stepKey="createConfigurations"> + <argument name="attributeCode" value="{{ProductAttributeFrontendLabel.label}}"/> + </actionGroup> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPage"/> + + <!-- Verify swatches are present in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute"/> + <grabAttributeFrom selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" userInput="style" stepKey="grabSwatch1"/> + <grabAttributeFrom selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '2')}}" userInput="style" stepKey="grabSwatch2"/> + <assertContains stepKey="assertSwatch1"> + <expectedResult type="string">adobe-thumb</expectedResult> + <actualResult type="string">{$grabSwatch1}</actualResult> + </assertContains> + <assertContains stepKey="assertSwatch2"> + <expectedResult type="string">adobe-small</expectedResult> + <actualResult type="string">{$grabSwatch2}</actualResult> + </assertContains> + + <!-- Click a swatch and expect to see the configurable product, not see the simple product --> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" stepKey="filterBySwatch1"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{BaseConfigurableProduct.name}}" stepKey="seeConfigurableProduct"/> + <dontSee selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeSimpleProduct"/> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml new file mode 100644 index 0000000000000..28df5ffd53436 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontFilterByTextSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="View swatches in product listing"/> + <title value="Customers can filter products using text swatches"/> + <description value="Customers can filter products using text swatches"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3462"/> + <group value="Swatches"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Begin creating a new product attribute --> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> + + <!-- Select text swatch --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_text" stepKey="selectInputType"/> + + <!-- Create swatch #1 --> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch0"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('0')}}" userInput="red" stepKey="fillSwatch0"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('0')}}" userInput="Something red." stepKey="fillDescription0"/> + + <!-- Create swatch #2 --> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('1')}}" userInput="blue" stepKey="fillSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('1')}}" userInput="Something blue." stepKey="fillDescription1"/> + + <!-- Set scope to global --> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + + <!-- Set Use In Layered Navigation --> + <scrollToTopOfPage stepKey="scrollToTop1"/> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="goToStorefrontProperties"/> + <selectOption selector="{{AttributePropertiesSection.useInLayeredNavigation}}" userInput="1" stepKey="selectUseInLayeredNavigation"/> + + <!-- Workaround: click on the main tab again to ensure the values are saved, otherwise the swatches do not get saved --> + <scrollToTopOfPage stepKey="scrollToTop2"/> + <click selector="{{AttributePropertiesSection.propertiesTab}}" stepKey="goBackToPropertiesTab"/> + + <!-- Save the new attribute --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGrid"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Create configurations based off the text swatch we created earlier --> + <actionGroup ref="createConfigurationsForAttribute" stepKey="createConfigurations"> + <argument name="attributeCode" value="{{ProductAttributeFrontendLabel.label}}"/> + </actionGroup> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPage"/> + + <!-- Verify swatches are present in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute"/> + <see selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" userInput="red" stepKey="seeRed"/> + <see selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '2')}}" userInput="blue" stepKey="seeBlue"/> + + <!-- Click a swatch and expect to see the configurable product, not see the simple product --> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" stepKey="filterBySwatch1"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{BaseConfigurableProduct.name}}" stepKey="seeConfigurableProduct"/> + <dontSee selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeSimpleProduct"/> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml new file mode 100644 index 0000000000000..d12cb0433fed1 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontFilterByVisualSwatchTest"> + <annotations> + <features value="Swatches"/> + <stories value="View swatches in product listing"/> + <title value="Customers can filter products using visual swatches"/> + <description value="Customers can filter products using visual swatches "/> + <severity value="CRITICAL"/> + <testCaseId value="MC-3082"/> + <group value="Swatches"/> + </annotations> + + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <!-- Begin creating a new product attribute --> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="fillDefaultLabel"/> + + <!-- Select visual swatch --> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="swatch_visual" stepKey="selectInputType"/> + + <!-- Set swatch #1 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch1"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('1')}}" stepKey="clickChooseColor1"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex1"> + <argument name="nthColorPicker" value="1"/> + <argument name="hexColor" value="e74c3c"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="red" stepKey="fillAdmin1"/> + + <!-- Set swatch #2 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="1"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('2')}}" stepKey="clickChooseColor2"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex2"> + <argument name="nthColorPicker" value="2"/> + <argument name="hexColor" value="3498db"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="blue" stepKey="fillAdmin2"/> + + <!-- Set scope to global --> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + + <!-- Set Use In Layered Navigation --> + <scrollToTopOfPage stepKey="scrollToTop1"/> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="goToStorefrontProperties"/> + <selectOption selector="{{AttributePropertiesSection.useInLayeredNavigation}}" userInput="1" stepKey="selectUseInLayeredNavigation"/> + + <!-- Save the new attribute --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad time="30" stepKey="waitForProductGrid"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Create configurations based off the visual watch we created earlier --> + <actionGroup ref="createConfigurationsForAttribute" stepKey="createConfigurations"> + <argument name="attributeCode" value="{{ProductAttributeFrontendLabel.label}}"/> + </actionGroup> + + <!-- Go to the category page --> + <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPage"/> + + <!-- Verify swatches are present in the layered navigation --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="{{ProductAttributeFrontendLabel.label}}" stepKey="seeAttributeInLayeredNav"/> + <click selector="{{StorefrontCategorySidebarSection.filterOptionTitle(ProductAttributeFrontendLabel.label)}}" stepKey="expandAttribute"/> + <grabAttributeFrom selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" userInput="style" stepKey="grabSwatch1"/> + <grabAttributeFrom selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '2')}}" userInput="style" stepKey="grabSwatch2"/> + <assertContains stepKey="assertSwatch1"> + <expectedResult type="string">rgb(231, 77, 60)</expectedResult> + <actualResult type="string">{$grabSwatch1}</actualResult> + </assertContains> + <assertContains stepKey="assertSwatch2"> + <expectedResult type="string">rgb(52, 152, 219)</expectedResult> + <actualResult type="string">{$grabSwatch2}</actualResult> + </assertContains> + + <!-- Click a swatch and expect to see the configurable product, not see the simple product --> + <click selector="{{StorefrontCategorySidebarSection.attributeNthOption(ProductAttributeFrontendLabel.label, '1')}}" stepKey="filterBySwatch1"/> + <see selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="{{BaseConfigurableProduct.name}}" stepKey="seeConfigurableProduct"/> + <dontSee selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" userInput="$$createSimpleProduct.name$$" stepKey="dontSeeSimpleProduct"/> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml new file mode 100644 index 0000000000000..7ef030ef8dfa8 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchProductWithFileCustomOptionTest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontSwatchProductWithFileCustomOptionTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Add configurable product to cart"/> + <title value="Configurable product with swatch option and file custom option"/> + <description value="Configurable product with swatch option and file custom option. When adding to cart with an invalid filetype, the correct error message is shown, and options remain selected."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-93101"/> + <group value="ConfigurableProduct"/> + <group value="Swatches"/> + </annotations> + + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create a configurable swatch product via the UI --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createCategory.name$$]" stepKey="searchAndSelectCategory"/> + <!--Add swatch attribute to configurable product--> + <actionGroup ref="AddVisualSwatchToProductActionGroup" stepKey="addSwatchToProduct"/> + <!--Add custom option to configurable product--> + <actionGroup ref="AddProductCustomOptionFile" stepKey="addCustomOptionToProduct"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + + <!--Go to storefront--> + <amOnPage url="" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageLoad"/> + <click selector="{{StorefrontNavigationSection.topCategory($$createCategory.name$$)}}" stepKey="goToCategoryStorefront"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.CategoryTitle}}" userInput="$$createCategory.name$$" stepKey="seeOnCategoryPage"/> + <!--Add configurable product to cart--> + <moveMouseOver selector="{{StorefrontCategoryProductSection.ProductTitleByName(BaseConfigurableProduct.name)}}" stepKey="hoverProductInGrid"/> + <click selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(BaseConfigurableProduct.name)}}" stepKey="tryAddToCartFromCategoryPage"/> + <waitForPageLoad stepKey="waitForRedirectToProductPage"/> + <seeInCurrentUrl url="{{StorefrontProductPage.url(BaseConfigurableProduct.name)}}" stepKey="seeOnProductPage"/> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel(visualSwatchOption2.default_label)}}" stepKey="clickSwatchOption"/> + <see selector="{{StorefrontProductInfoMainSection.selectedSwatchValue(visualSwatchAttribute.default_label)}}" userInput="{{visualSwatchOption2.default_label}}" stepKey="seeSwatchIsSelected"/> + + <!--Try invalid file--> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(ProductOptionFile.title)}}" userInput="lorem_ipsum.docx" stepKey="attachInvalidFile"/> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCartInvalidFile"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.alertMessage}}" stepKey="waitForErrorMessageInvalidFile"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="The file 'lorem_ipsum.docx' for '{{ProductOptionFile.title}}' has an invalid extension." stepKey="seeMessageInvalidFile"/> + <!--Swatch remains selected--> + <see selector="{{StorefrontProductInfoMainSection.selectedSwatchValue(visualSwatchAttribute.default_label)}}" userInput="{{visualSwatchOption2.default_label}}" stepKey="seeSwatchRemainsSelected"/> + <!--Try valid file--> + <attachFile selector="{{StorefrontProductInfoMainSection.addLinkFileUploadFile(ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="attachValidFile"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="$132.99" stepKey="seePriceUpdated"/> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCartValidFile"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.successMsg}}" stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontProductPageSection.messagesBlock}}" userInput="You added {{BaseConfigurableProduct.name}} to your shopping cart." stepKey="seeSuccessMessage"/> + + <!--Check item in cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <seeElement selector="{{CheckoutCartProductSection.ProductLinkByName(BaseConfigurableProduct.name)}}" stepKey="seeProductInCart"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, visualSwatchAttribute.default_label)}}" userInput="{{visualSwatchOption2.default_label}}" stepKey="seeSelectedSwatch"/> + <see selector="{{CheckoutCartProductSection.ProductOptionByNameAndAttribute(BaseConfigurableProduct.name, ProductOptionFile.title)}}" userInput="{{MagentoLogo.file}}" stepKey="seeCorrectOptionFile"/> + <!--Delete cart item--> + <click selector="{{CheckoutCartProductSection.RemoveItem}}" stepKey="deleteCartItem"/> + + <!--Delete product--> + <actionGroup ref="deleteProductBySku" stepKey="deleteProduct"> + <argument name="sku" value="{{BaseConfigurableProduct.sku}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/composer.json b/app/code/Magento/Swatches/Test/Mftf/composer.json deleted file mode 100644 index 76c8037995754..0000000000000 --- a/app/code/Magento/Swatches/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-swatches", - "description": "Add Swatches to Products", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-layered-navigation": "100.0.0-dev", - "magento/functional-test-module-swatches-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index 71f9fc8f80baa..0940003b6a72d 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -23,7 +23,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Swatches/etc/db_schema_whitelist.json b/app/code/Magento/Swatches/etc/db_schema_whitelist.json index 8f335442def21..e1b8e4c7e1798 100644 --- a/app/code/Magento/Swatches/etc/db_schema_whitelist.json +++ b/app/code/Magento/Swatches/etc/db_schema_whitelist.json @@ -1,25 +1,25 @@ { - "catalog_eav_attribute": { - "column": { - "additional_data": true - } - }, - "eav_attribute_option_swatch": { - "column": { - "swatch_id": true, - "option_id": true, - "store_id": true, - "type": true, - "value": true - }, - "index": { - "EAV_ATTRIBUTE_OPTION_SWATCH_SWATCH_ID": true + "catalog_eav_attribute": { + "column": { + "additional_data": true + } }, - "constraint": { - "PRIMARY": true, - "EAV_ATTRIBUTE_OPTION_SWATCH_STORE_ID_STORE_STORE_ID": true, - "EAV_ATTR_OPT_SWATCH_OPT_ID_EAV_ATTR_OPT_OPT_ID": true, - "EAV_ATTRIBUTE_OPTION_SWATCH_STORE_ID_OPTION_ID": true + "eav_attribute_option_swatch": { + "column": { + "swatch_id": true, + "option_id": true, + "store_id": true, + "type": true, + "value": true + }, + "index": { + "EAV_ATTRIBUTE_OPTION_SWATCH_SWATCH_ID": true + }, + "constraint": { + "PRIMARY": true, + "EAV_ATTRIBUTE_OPTION_SWATCH_STORE_ID_STORE_STORE_ID": true, + "EAV_ATTR_OPT_SWATCH_OPT_ID_EAV_ATTR_OPT_OPT_ID": true, + "EAV_ATTRIBUTE_OPTION_SWATCH_STORE_ID_OPTION_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml index fc1de530a66bd..b044e692313dc 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml @@ -20,6 +20,9 @@ "gallerySwitchStrategy": "<?php /* @escapeNotVerified */ echo $block->getVar('gallery_switch_strategy', 'Magento_ConfigurableProduct') ?: 'replace'; ?>" } + }, + "*" : { + "Magento_Swatches/js/catalog-add-to-cart": {} } } </script> diff --git a/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js index d699faae3a85f..80003ef68f1c4 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/catalog-add-to-cart.js @@ -7,11 +7,23 @@ require([ ], function ($) { 'use strict'; + /** + * Add selected swatch attributes to redirect url + * + * @see Magento_Catalog/js/catalog-add-to-cart + */ $('body').on('catalogCategoryAddToCartRedirect', function (event, data) { $(data.form).find('[name*="super"]').each(function (index, item) { - var $item = $(item); + var $item = $(item), + attr; + + if ($item.attr('data-attr-name')) { + attr = $item.attr('data-attr-name'); + } else { + attr = $item.parent().attr('attribute-code'); + } + data.redirectParameters.push(attr + '=' + $item.val()); - data.redirectParameters.push($item.attr('data-attr-name') + '=' + $item.val()); }); }); }); diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index a76f070500797..4a57bf796aabd 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -1214,8 +1214,20 @@ define([ */ _EmulateSelected: function (selectedAttributes) { $.each(selectedAttributes, $.proxy(function (attributeCode, optionId) { - this.element.find('.' + this.options.classes.attributeClass + - '[attribute-code="' + attributeCode + '"] [option-id="' + optionId + '"]').trigger('click'); + var elem = this.element.find('.' + this.options.classes.attributeClass + + '[attribute-code="' + attributeCode + '"] [option-id="' + optionId + '"]'), + parentInput = elem.parent(); + + if (elem.hasClass('selected')) { + return; + } + + if (parentInput.hasClass(this.options.classes.selectClass)) { + parentInput.val(optionId); + parentInput.trigger('change'); + } else { + elem.trigger('click'); + } }, this)); }, diff --git a/app/code/Magento/SwatchesGraphQl/Test/Mftf/composer.json b/app/code/Magento/SwatchesGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 7a62d9bd33ab9..0000000000000 --- a/app/code/Magento/SwatchesGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-swatches-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-swatches": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json b/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json deleted file mode 100644 index e37d2bfda247d..0000000000000 --- a/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-swatches-layered-navigation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Tax/Block/Adminhtml/Rate/Form.php b/app/code/Magento/Tax/Block/Adminhtml/Rate/Form.php index 96fc9a40d53d1..450203486f364 100644 --- a/app/code/Magento/Tax/Block/Adminhtml/Rate/Form.php +++ b/app/code/Magento/Tax/Block/Adminhtml/Rate/Form.php @@ -31,7 +31,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic /** * @var string */ - protected $_template = 'rate/form.phtml'; + protected $_template = 'Magento_Tax::rate/form.phtml'; /** * Tax data diff --git a/app/code/Magento/Tax/Block/Adminhtml/Rate/Title.php b/app/code/Magento/Tax/Block/Adminhtml/Rate/Title.php index e1e866c06571b..9612b57f8d5d8 100644 --- a/app/code/Magento/Tax/Block/Adminhtml/Rate/Title.php +++ b/app/code/Magento/Tax/Block/Adminhtml/Rate/Title.php @@ -23,7 +23,7 @@ class Title extends \Magento\Framework\View\Element\Template /** * @var string */ - protected $_template = 'rate/title.phtml'; + protected $_template = 'Magento_Tax::rate/title.phtml'; /** * @var \Magento\Store\Model\StoreFactory diff --git a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Add.php b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Add.php index 9cf96bc21e962..16d828542c5b9 100644 --- a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Add.php +++ b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Add.php @@ -20,7 +20,7 @@ class Add extends \Magento\Backend\Block\Template implements \Magento\Backend\Bl /** * @var string */ - protected $_template = 'toolbar/rate/add.phtml'; + protected $_template = 'Magento_Tax::toolbar/rate/add.phtml'; /** * @var \Magento\Backend\Block\Widget\Button\ButtonList diff --git a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php index 19c5fab72ac4b..4eaaa3be8a8f2 100644 --- a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php +++ b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php @@ -16,7 +16,7 @@ class Save extends \Magento\Backend\Block\Template implements \Magento\Backend\B /** * @var string */ - protected $_template = 'toolbar/rate/save.phtml'; + protected $_template = 'Magento_Tax::toolbar/rate/save.phtml'; /** * @var \Magento\Backend\Block\Widget\Button\ButtonList diff --git a/app/code/Magento/Tax/Block/Checkout/Grandtotal.php b/app/code/Magento/Tax/Block/Checkout/Grandtotal.php index 68de4cb24a487..77af1ad99ea2c 100644 --- a/app/code/Magento/Tax/Block/Checkout/Grandtotal.php +++ b/app/code/Magento/Tax/Block/Checkout/Grandtotal.php @@ -15,7 +15,7 @@ class Grandtotal extends \Magento\Checkout\Block\Total\DefaultTotal * * @var string */ - protected $_template = 'checkout/grandtotal.phtml'; + protected $_template = 'Magento_Tax::checkout/grandtotal.phtml'; /** * @var \Magento\Tax\Model\Config diff --git a/app/code/Magento/Tax/Block/Checkout/Shipping.php b/app/code/Magento/Tax/Block/Checkout/Shipping.php index e9098035053be..299c586fd224c 100644 --- a/app/code/Magento/Tax/Block/Checkout/Shipping.php +++ b/app/code/Magento/Tax/Block/Checkout/Shipping.php @@ -15,7 +15,7 @@ class Shipping extends \Magento\Checkout\Block\Total\DefaultTotal * * @var string */ - protected $_template = 'checkout/shipping.phtml'; + protected $_template = 'Magento_Tax::checkout/shipping.phtml'; /** * @var \Magento\Tax\Model\Config diff --git a/app/code/Magento/Tax/Block/Checkout/Subtotal.php b/app/code/Magento/Tax/Block/Checkout/Subtotal.php index 7a9059df08bab..22da07954159d 100644 --- a/app/code/Magento/Tax/Block/Checkout/Subtotal.php +++ b/app/code/Magento/Tax/Block/Checkout/Subtotal.php @@ -15,7 +15,7 @@ class Subtotal extends \Magento\Checkout\Block\Total\DefaultTotal * * @var string */ - protected $_template = 'checkout/subtotal.phtml'; + protected $_template = 'Magento_Tax::checkout/subtotal.phtml'; /** * @var \Magento\Tax\Model\Config diff --git a/app/code/Magento/Tax/Block/Checkout/Tax.php b/app/code/Magento/Tax/Block/Checkout/Tax.php index f741e64019de1..0a86c0312ab1c 100644 --- a/app/code/Magento/Tax/Block/Checkout/Tax.php +++ b/app/code/Magento/Tax/Block/Checkout/Tax.php @@ -14,5 +14,5 @@ class Tax extends \Magento\Checkout\Block\Total\DefaultTotal /** * @var string */ - protected $_template = 'checkout/tax.phtml'; + protected $_template = 'Magento_Tax::checkout/tax.phtml'; } diff --git a/app/code/Magento/Tax/Block/Sales/Order/Tax.php b/app/code/Magento/Tax/Block/Sales/Order/Tax.php index 71eb629111344..4d48abbcb0982 100644 --- a/app/code/Magento/Tax/Block/Sales/Order/Tax.php +++ b/app/code/Magento/Tax/Block/Sales/Order/Tax.php @@ -264,12 +264,6 @@ protected function _initShipping() */ protected function _initDiscount() { - // $store = $this->getStore(); - // $parent = $this->getParentBlock(); - // if ($this->_config->displaySales) { - // - // } elseif ($this->_config->displaySales) { - // } } /** diff --git a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php index afcfa1bbebcb0..bad64260cf58a 100644 --- a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php +++ b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php @@ -10,7 +10,7 @@ abstract class AbstractAggregateCalculator extends AbstractCalculator { /** - * {@inheritdoc} + * @inheritdoc */ protected function calculateWithTaxInPrice(QuoteDetailsItemInterface $item, $quantity, $round = true) { @@ -86,7 +86,7 @@ protected function calculateWithTaxInPrice(QuoteDetailsItemInterface $item, $qua } /** - * {@inheritdoc} + * @inheritdoc */ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $quantity, $round = true) { @@ -149,6 +149,7 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $ $rowTaxBeforeDiscount = array_sum($rowTaxesBeforeDiscount); $rowTotalInclTax = $rowTotal + $rowTaxBeforeDiscount; $priceInclTax = $rowTotalInclTax / $quantity; + if ($round) { $priceInclTax = $this->calculationTool->round($priceInclTax); } diff --git a/app/code/Magento/Tax/Model/Plugin/OrderSave.php b/app/code/Magento/Tax/Model/Plugin/OrderSave.php index a1a3ebf861d60..38952eec02ca1 100644 --- a/app/code/Magento/Tax/Model/Plugin/OrderSave.php +++ b/app/code/Magento/Tax/Model/Plugin/OrderSave.php @@ -7,6 +7,8 @@ namespace Magento\Tax\Model\Plugin; +use Magento\Tax\Api\Data\OrderTaxDetailsAppliedTaxExtension; + class OrderSave { /** @@ -79,8 +81,9 @@ protected function saveOrderTax(\Magento\Sales\Api\Data\OrderInterface $order) foreach ($taxesForItems as $taxesArray) { foreach ($taxesArray['applied_taxes'] as $rates) { if (isset($rates['extension_attributes'])) { - /** @var \Magento\Tax\Api\Data\AppliedTaxRateInterface[] $taxRates */ - $taxRates = $rates['extension_attributes']->getRates(); + $taxRates = $rates['extension_attributes'] instanceof OrderTaxDetailsAppliedTaxExtension + ? $rates['extension_attributes']->getRates() + : $rates['extension_attributes']['rates']; if (is_array($taxRates)) { if (count($taxRates) == 1) { $ratesIdQuoteItemId[$rates['id']][] = [ @@ -124,8 +127,9 @@ protected function saveOrderTax(\Magento\Sales\Api\Data\OrderInterface $order) foreach ($taxes as $row) { $id = $row['id']; if (isset($row['extension_attributes'])) { - /** @var \Magento\Tax\Api\Data\AppliedTaxRateInterface[] $taxRates */ - $taxRates = $row['extension_attributes']->getRates(); + $taxRates = $row['extension_attributes'] instanceof OrderTaxDetailsAppliedTaxExtension + ? $row['extension_attributes']->getRates() + : $row['extension_attributes']['rates']; if (is_array($taxRates)) { foreach ($taxRates as $tax) { if ($row['percent'] == null) { diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/Shipping.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/Shipping.php index 16a55dbfac3e2..ddfb6f9fd5073 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/Shipping.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/Shipping.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Tax\Model\Sales\Total\Quote; use Magento\Quote\Model\Quote\Address; @@ -39,16 +41,23 @@ public function collect( $quoteDetails = $this->prepareQuoteDetails($shippingAssignment, [$shippingDataObject]); $taxDetails = $this->taxCalculationService ->calculateTax($quoteDetails, $storeId); + $taxDetailsItems = $taxDetails->getItems()[self::ITEM_CODE_SHIPPING]; $baseQuoteDetails = $this->prepareQuoteDetails($shippingAssignment, [$baseShippingDataObject]); $baseTaxDetails = $this->taxCalculationService ->calculateTax($baseQuoteDetails, $storeId); + $baseTaxDetailsItems = $baseTaxDetails->getItems()[self::ITEM_CODE_SHIPPING]; + + $quote->getShippingAddress() + ->setShippingAmount($taxDetailsItems->getRowTotal()); + $quote->getShippingAddress() + ->setBaseShippingAmount($baseTaxDetailsItems->getRowTotal()); $this->processShippingTaxInfo( $shippingAssignment, $total, - $taxDetails->getItems()[self::ITEM_CODE_SHIPPING], - $baseTaxDetails->getItems()[self::ITEM_CODE_SHIPPING] + $taxDetailsItems, + $baseTaxDetailsItems ); return $this; @@ -58,6 +67,8 @@ public function collect( * @param \Magento\Quote\Model\Quote $quote * @param Address\Total $total * @return array|null + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total) { diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml index 6d6e5cea0fd34..6c535e3004e69 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Change the tax configuration to display in cart and checkout flow --> <actionGroup name="editTaxConfigurationByUI"> <!-- navigate to the tax configuration page --> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml index e786616119c92..42fd01357375e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxCodeData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="SimpleTaxNY" type="tax"> <data key="state">New York</data> <data key="country">United States</data> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxConfigData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxConfigData.xml new file mode 100644 index 0000000000000..4edf005c2fc2b --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxConfigData.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <!-- Default Tax Destination Calculation --> + <entity name="CountryUS" type="country"> + <data key="value">US</data> + </entity> + <entity name="AllPostCode" type="postcode"> + <data key="value">*</data> + </entity> + <!-- Shopping Cart Display Settings --> + <entity name="IncludeTaxInOrderTotalCart" type="grandtotalCart"> + <data key="value">1</data> + </entity> + <entity name="DisplayFullTaxSummaryCart" type="full_summaryCart"> + <data key="value">1</data> + </entity> + <entity name="DisplayZeroTaxSubtotalCart" type="zero_taxCart"> + <data key="value">1</data> + </entity> + <entity name="Tax_Config_CA" type="tax_config_state"> + <!-- Default Tax Destination Calculation --> + <requiredEntity type="country">CountryUS</requiredEntity> + <requiredEntity type="region">Region_CA</requiredEntity> + <requiredEntity type="postcode">AllPostCode</requiredEntity> + <!-- Shopping Cart Display Settings --> + <requiredEntity type="grandtotalCart">IncludeTaxInOrderTotalCart</requiredEntity> + <requiredEntity type="full_summaryCart">DisplayFullTaxSummaryCart</requiredEntity> + <requiredEntity type="zero_taxCart">DisplayZeroTaxSubtotalCart</requiredEntity> + </entity> + + <entity name="Tax_Config_NY" type="tax_config_state"> + <!-- Default Tax Destination Calculation --> + <requiredEntity type="country">CountryUS</requiredEntity> + <requiredEntity type="region">Region_NY</requiredEntity> + <requiredEntity type="postcode">AllPostCode</requiredEntity> + <!-- Shopping Cart Display Settings --> + <requiredEntity type="grandtotalCart">IncludeTaxInOrderTotalCart</requiredEntity> + <requiredEntity type="full_summaryCart">DisplayFullTaxSummaryCart</requiredEntity> + <requiredEntity type="zero_taxCart">DisplayZeroTaxSubtotalCart</requiredEntity> + </entity> + <!-- Set default settings --> + <entity name="DefaultTaxConfig" type="tax_config_default"> + <requiredEntity type="taxTotalFlagZero">DefaultTotalFlagZero</requiredEntity> + <requiredEntity type="taxPostCodeEmpty">EmptyField</requiredEntity> + </entity> + <entity name="DefaultTotalFlagZero" type="taxTotalFlagZero"> + <data key="value">0</data> + </entity> + <entity name="EmptyField" type="taxPostCodeEmpty"> + <data key="value"/> + </entity> +</entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRegionData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRegionData.xml new file mode 100644 index 0000000000000..353bc0a489443 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRegionData.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="Region_NY" type="region"> + <data key="value">43</data> + </entity> + <entity name="Region_CA" type="region"> + <data key="value">12</data> + </entity> +</entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml new file mode 100644 index 0000000000000..b3f341b687ba7 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRuleData.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SimpleTaxRule" type="taxRule"> + <data key="code" unique="suffix">TaxRule</data> + <data key="position">0</data> + <data key="priority">0</data> + <array key="customer_tax_class_ids"> + <item>3</item> + </array> + <array key="product_tax_class_ids"> + <item>2</item> + </array> + <array key="tax_rate_ids"> + <item>1</item> + <item>2</item> + </array> + <data key="calculate_subtotal">true</data> + </entity> +</entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Metadata/tax_config-meta.xml b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_config-meta.xml new file mode 100644 index 0000000000000..7383e9c580283 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_config-meta.xml @@ -0,0 +1,183 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateTaxConfigDefaultsTaxDestination" dataType="tax_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/tax/" method="POST"> + <object key="groups" dataType="tax_config_state"> + <object key="defaults" dataType="tax_config_state"> + <object key="fields" dataType="tax_config_state"> + <object key="country" dataType="country"> + <field key="value">string</field> + </object> + <object key="region" dataType="region"> + <field key="value">string</field> + </object> + <object key="postcode" dataType="postcode"> + <field key="value">string</field> + </object> + </object> + </object> + <object key="cart_display" dataType="tax_config_state"> + <object key="fields" dataType="tax_config_state"> + <object key="grandtotal" dataType="grandtotalCart"> + <field key="value">string</field> + </object> + <object key="full_summary" dataType="full_summaryCart"> + <field key="value">string</field> + </object> + <object key="zero_tax" dataType="zero_taxCart"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> + <operation name="TaxConfigDefaultsTaxDestination" dataType="tax_config_default" type="create" auth="adminFormKey" url="/admin/system_config/save/section/tax/" method="POST"> + <object key="groups" dataType="tax_config_default"> + <object key="calculation" dataType="tax_config_default"> + <object key="fields" dataType="tax_config_default"> + <object key="algorithm" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="based_on" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="price_includes_tax" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="shipping_includes_tax" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="apply_after_discount" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="discount_tax" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="apply_tax_on" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="cross_border_trade_enabled" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + </object> + <object key="defaults" dataType="tax_config_default"> + <object key="fields" dataType="tax_config_default"> + <object key="country" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="region" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="postcode" dataType="taxPostCodeEmpty"> + <field key="value">string</field> + </object> + </object> + </object> + <object key="cart_display" dataType="tax_config_default"> + <object key="fields" dataType="tax_config_default"> + <object key="price" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="subtotal" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="shipping" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="gift_wrapping" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + <object key="printed_card" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + <object key="grandtotal" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="full_summary" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="zero_tax" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + <object key="sales_display" dataType="tax_config_default"> + <object key="fields" dataType="tax_config_default"> + <object key="price" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="subtotal" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="shipping" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="gift_wrapping" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + <object key="printed_card" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + <object key="grandtotal" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="full_summary" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + <object key="zero_tax" dataType="tax_config_default"> + <object key="inherit" dataType="taxTotalFlagZero"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rule-meta.xml b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rule-meta.xml new file mode 100644 index 0000000000000..c5b781519912d --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rule-meta.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateTaxRule" dataType="taxRule" type="create" auth="adminOauth" url="/V1/taxRules" method="POST"> + <contentType>application/json</contentType> + <object key="rule" dataType="taxRule"> + <field key="id">integer</field> + <field key="code" required="true">string</field> + <field key="priority">integer</field> + <field key="position">integer</field> + <array key="customer_tax_class_ids"> + <value>integer</value> + </array> + <array key="product_tax_class_ids"> + <value>integer</value> + </array> + <array key="tax_rate_ids"> + <value>integer</value> + </array> + <field key="calculate_subtotal">boolean</field> + <field key="extension_attributes">empty_extension_attribute</field> + </object> + </operation> + <operation name="DeleteTaxRule" dataType="taxRule" type="delete" auth="adminOauth" url="/V1/taxRules/{id}" method="DELETE"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminNewTaxRulePage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminNewTaxRulePage.xml index cf7fcf041c1b1..6aedc0014280c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Page/AdminNewTaxRulePage.xml +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminNewTaxRulePage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminNewTaxRulePage" url="tax/rule/new/" module="Magento_Tax" area="admin"> <section name="AdminTaxRulesSection"/> </page> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxConfigurationPage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxConfigurationPage.xml index d18e300983b5f..a9d14de18de80 100644 --- a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxConfigurationPage.xml +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxConfigurationPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminTaxConfigurationPage" url="admin/system_config/edit/section/tax/" area="admin" module="Magento_Tax"> <section name="AdminConfigureTaxSection"/> </page> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRateGridPage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRateGridPage.xml index 6073766a81692..716c399110470 100644 --- a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRateGridPage.xml +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRateGridPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminTaxRateGridPage" url="tax/rate/" area="admin" module="Magento_Tax"> <section name="AdminSecondaryGridSection"/> </page> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRuleGridPage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRuleGridPage.xml index 8c75237a203a6..3c8326e95d930 100644 --- a/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRuleGridPage.xml +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminTaxRuleGridPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminTaxRuleGridPage" url="tax/rule" area="admin" module="Magento_Tax"> <section name="AdminSecondaryGridSection"/> <section name="AdminGridMainControls"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml index ad68179119834..896d719a436ca 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml @@ -7,18 +7,33 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminConfigureTaxSection"> <!-- on page /admin/admin/system_config/edit/section/tax/ --> <element name="taxClasses" type="block" selector="#tax_classes-head" timeout="30"/> - <element name="calculationSettings" type="block" selector="#tax_calculation-head:not(.open)" timeout="30"/> - + + <element name="taxCalculationSettings" type="block" selector="#tax_calculation-head" timeout="30"/> + <element name="taxCalculationSettingsOpened" type="block" selector="#tax_calculation-head.open" timeout="30"/> + <element name="taxCalculationAlgorithm" type="select" selector="#tax_calculation_algorithm"/> + <element name="taxCalculationAlgorithmDisabled" type="select" selector="#tax_calculation_algorithm[disabled='disabled']"/> + <element name="taxCalculationAlgorithmInherit" type="checkbox" selector="#tax_calculation_algorithm_inherit"/> + <element name="taxCalculationBased" type="select" selector="#tax_calculation_based_on"/> + <element name="taxCalculationBasedDisabled" type="select" selector="#tax_calculation_based_on[disabled='disabled']"/> + <element name="taxCalculationBasedInherit" type="checkbox" selector="#tax_calculation_based_on_inherit"/> + <element name="taxCalculationPrices" type="select" selector="#tax_calculation_price_includes_tax"/> + <element name="taxCalculationPricesDisabled" type="select" selector="#tax_calculation_price_includes_tax[disabled='disabled']"/> + <element name="taxCalculationPricesInherit" type="checkbox" selector="#tax_calculation_price_includes_tax_inherit"/> + <element name="defaultDestination" type="block" selector="#tax_defaults-head" timeout="30"/> <element name="systemValueDefaultState" type="checkbox" selector="#row_tax_defaults_region input[type='checkbox']"/> <element name="dropdownDefaultState" type="select" selector="#row_tax_defaults_region select"/> - <element name="defaultPostCode" type="input" selector="#tax_defaults_postcode"/> + <element name="defaultPostCode" type="checkbox" selector="#tax_defaults_postcode"/> - <element name="priceDisplaySettings" type="block" selector="#tax_display-head:not(.open)" timeout="30"/> + <element name="taxPriceDisplaySettings" type="block" selector="#tax_display-head" timeout="30"/> + <element name="taxPriceDisplaySettingsOpened" type="block" selector="#tax_display-head.open" timeout="30"/> + <element name="taxDisplayProductPrices" type="select" selector="#tax_display_type"/> + <element name="taxDisplayProductPricesDisabled" type="select" selector="#tax_display_type[disabled='disabled']"/> + <element name="taxDisplayProductPricesInherit" type="checkbox" selector="#tax_display_type_inherit"/> <element name="shoppingCartDisplay" type="block" selector="#tax_cart_display-head" timeout="30"/> <element name="systemValueIncludeTaxTotalCart" type="checkbox" selector="#row_tax_cart_display_grandtotal input[type='checkbox']"/> @@ -35,7 +50,6 @@ <element name="dropdownDisplayTaxSummarySales" type="checkbox" selector="#row_tax_sales_display_full_summary select"/> <element name="systemValueDisplayZeroTaxSales" type="checkbox" selector="#row_tax_sales_display_zero_tax input[type='checkbox']"/> <element name="dropdownDisplayZeroTaxSales" type="checkbox" selector="#row_tax_sales_display_zero_tax select"/> - - <element name="priceDisplaySettings" type="block" selector="#tax_weee-head" timeout="30"/> + <element name="fixedProductTaxes" type="block" selector="#tax_weee-head" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml index f7b72263c1255..9727c649d7e66 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminTaxRulesSection"> <!-- on page /admin/tax/rule/new/ --> <element name="ruleName" type="input" selector="#anchor-content #code"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml new file mode 100644 index 0000000000000..b89a77b8ad2ca --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CheckoutCartSummarySection"> + <element name="taxAmount" type="text" selector="[data-th='Tax']>span"/> + <element name="taxSummary" type="text" selector=".totals-tax-summary"/> + <element name="rate" type="text" selector=" tr.totals-tax-details.shown th.mark"/> + </section> +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml new file mode 100644 index 0000000000000..1b3422011a9a7 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest"> + <annotations> + <features value="Tax"/> + <stories value="Shopping cart taxes"/> + <title value="Tax information are updating/recalculating on fly in shopping cart for Customer with default addresses (physical quote)"/> + <description value="Tax information are updating/recalculating on fly in shopping cart for Customer with default addresses (physical quote)"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-41932"/> + <group value="checkout"/> + <group value="tax"/> + </annotations> + <before> + <!-- Preconditions --> + <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> + <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> + <!-- Fixed Product Tax attribute is created and added to default attribute set --> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <!-- Tax configuration (Store>Configuration; Sales>Tax) With FPT Enable --> + <createData entity="Tax_Config_NY" stepKey="taxConfigurationNYWithFPTEnable"/> + <!-- Store>Configuration; Sales>Tax FPT Enable --> + <createData entity="WeeeConfigEnable" stepKey="enableFPT"/> + <!-- Simple product is created Price = 10; FPT United States/California/10,United States/New York/20 --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <field key="price">10.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Customer is created with default addresses: --> + <createData entity="Simple_US_Customer_CA" stepKey="createCustomer"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addFPTValue1"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="California"/> + <argument name="valueForFPT" value="10"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addFPTValue2"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="New York"/> + <argument name="valueForFPT" value="20"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + </before> + <after> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> + <createData entity="WeeeConfigDisable" stepKey="disableFPT"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Go to Storefront as logged in Customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + <!-- Step 2: Add simple product to shopping cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!-- Step 3: Go to Shopping Cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <!-- Step 4: Open Estimate Shipping and Tax section --> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> + <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> + <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkFPTAmountCA" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <!-- Step 5: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <!-- Step 6: Select shipping rate again(it need for get new totals request - performance reason) --> + <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <dontSeeElement selector="{{CheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> + <!-- Step 7: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <!-- Step 8: Select shipping rate again(it need for get new totals request - performance reason) --> + <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary3"/> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkFPTAmountNY" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml new file mode 100644 index 0000000000000..3fa9826512934 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest"> + <annotations> + <features value="Tax"/> + <stories value="Shopping cart taxes"/> + <title value="Tax information are updating/recalculating on fly in shopping cart for Customer with default addresses (virtual quote)"/> + <description value="Tax information are updating/recalculating on fly in shopping cart for Customer with default addresses (virtual quote)"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-41933"/> + <group value="checkout"/> + <group value="tax"/> + </annotations> + <before> + <!-- Preconditions --> + <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> + <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> + <!-- Fixed Product Tax attribute is created and added to default attribute set --> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <!-- Tax configuration (Store>Configuration; Sales>Tax) --> + <createData entity="Tax_Config_CA" stepKey="taxConfigurationCA"/> + <!-- Virtual product is created: --> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <field key="price">40.00</field> + </createData> + <!-- Customer is created with default addresses: --> + <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Go to Storefront as logged in Customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$" /> + </actionGroup> + <!-- Step 2: Add virtual product to shopping cart --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.name$$)}}" stepKey="amOnStorefrontVirtualProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddVirtualProductToCart"> + <argument name="product" value="$$createVirtualProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!-- Step 3: Go to Shopping Cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <!-- Step 4: Open Estimate Shipping and Tax section --> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> + <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> + <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="expandTaxSummary"/> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <!-- Step 5: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <!-- Step 6: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="California" stepKey="selectCaliforniaRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="90230" stepKey="inputPostCode2"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmount2" /> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml new file mode 100644 index 0000000000000..47161001219e8 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest"> + <annotations> + <features value="Tax"/> + <stories value="Shopping cart taxes"/> + <title value="Tax information are updating/recalculating on fly in shopping cart for Guest (physical quote)"/> + <description value="Tax information are updating/recalculating on fly in shopping cart for Guest (physical quote)"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-41930"/> + <!--Skip because of issue MAGETWO-90966 on 4 step--> + <group value="skip"/> + <group value="checkout"/> + <group value="tax"/> + </annotations> + <before> + <!-- Preconditions --> + <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> + <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> + <!-- Fixed Product Tax attribute is created and added to default attribute set --> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <!-- Tax configuration (Store>Configuration; Sales>Tax) --> + <createData entity="Tax_Config_CA" stepKey="taxConfigurationForCA"/> + <!-- Store>Configuration; Sales>Tax FPT Enable --> + <createData entity="WeeeConfigEnable" stepKey="enableFPT"/> + <!-- Simple product is created Price = 10; FPT United States/California/10,United States/New York/20 --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <field key="price">10.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addFPTValue1"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="California"/> + <argument name="valueForFPT" value="10"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addFPTValue2"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="New York"/> + <argument name="valueForFPT" value="20"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetGridToDefaultKeywordSearch"/> + </before> + <after> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> + <createData entity="WeeeConfigDisable" stepKey="disableFPT"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Go to Storefront as Guest --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <!-- Step 2: Add simple product to shopping cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!-- Step 3: Go to Shopping Cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <!-- Step 4: Open Estimate Shipping and Tax section --> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkFPTAmountCA" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <!-- Step 5: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <!-- Step 6: Select shipping rate again(it need for get new totals request - performance reason) --> + <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <dontSeeElement selector="{{CheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> + <!-- Step 7: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <!-- Step 8: Select shipping rate again(it need for get new totals request - performance reason) --> + <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkFPTAmountNY" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml new file mode 100644 index 0000000000000..88496b8e2cd27 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest"> + <annotations> + <features value="Tax"/> + <stories value="Shopping cart taxes"/> + <title value="Tax information are updating/recalculating on fly in shopping cart for Guest (virtual quote)"/> + <description value="Tax information are updating/recalculating on fly in shopping cart for Guest (virtual quote)"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-41931"/> + <!--Skip because of issue MAGETWO-90966 on 4 step--> + <group value="skip"/> + <group value="checkout"/> + <group value="tax"/> + </annotations> + <before> + <!-- Preconditions --> + <!-- Tax Rule is created based on default tax rates (Stores>Tax Rule) US-CA-*-Rate 1 = 8.2500 US-NY-*-Rate 1 = 8.3750 --> + <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> + <!-- Fixed Product Tax attribute is created and added to default attribute set --> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <!-- Tax configuration (Store>Configuration; Sales>Tax) --> + <createData entity="Tax_Config_CA" stepKey="taxConfigurationCA"/> + <!-- Virtual product is created: Price = 10 --> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <field key="price">40.00</field> + </createData> + </before> + <after> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Go to Storefront as Guest --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <!-- Step 2: Add virtual product to shopping cart --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.name$$)}}" stepKey="amOnStorefrontVirtualProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddVirtualProductToCart"> + <argument name="product" value="$$createVirtualProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <!-- Step 3: Go to Shopping Cart --> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> + <!-- Step 4: Open Estimate Shipping and Tax section --> + <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmountCA" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <!-- Step 5: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <!-- Step 6: Change Data --> + <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary2" /> + <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.35" stepKey="checkTaxAmountNY" /> + <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml index 752585830638f..3b741e7bf79ec 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontTaxQuoteCartLoggedInSimple"> <annotations> <features value="Tax"/> @@ -248,8 +248,6 @@ <severity value="CRITICAL"/> <testCaseId value="MC-297"/> <group value="Tax"/> - <group value="skip"/> - <!-- skipped due to MAGETWO-90539 --> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -356,8 +354,6 @@ <severity value="CRITICAL"/> <testCaseId value="MC-298"/> <group value="Tax"/> - <group value="skip"/> - <!-- Skipped due to MAGETWO-90539 --> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml index f051d4f8c3b82..000fba1d88697 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontTaxQuoteCheckoutGuestVirtual"> <annotations> <features value="Tax"/> @@ -313,7 +313,7 @@ </actionGroup> <click stepKey="clickNext" selector="{{CheckoutShippingSection.next}}"/> <see stepKey="seeAddress" selector="{{CheckoutShippingSection.defaultShipping}}" userInput="{{SimpleTaxCA.state}}"/> - <see stepKey="seeShipTo" selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{SimpleTaxCA.state}}"/> + <see stepKey="seeShipTo" selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{SimpleTaxCA.state}}"/> <!-- Assert that taxes are applied correctly for CA --> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> @@ -329,7 +329,7 @@ <argument name="Address" value="US_Address_NY"/> </actionGroup> <click stepKey="clickNext2" selector="{{CheckoutShippingSection.next}}"/> - <see stepKey="seeShipTo2" selector="{{CheckoutPaymentSection.shipToInfomation}}" userInput="{{SimpleTaxNY.state}}"/> + <see stepKey="seeShipTo2" selector="{{CheckoutPaymentSection.shipToInformation}}" userInput="{{SimpleTaxNY.state}}"/> <!-- Assert that taxes are applied correctly for NY --> <waitForElementVisible stepKey="waitForOverviewVisible2" selector="{{CheckoutPaymentSection.tax}}"/> diff --git a/app/code/Magento/Tax/Test/Mftf/composer.json b/app/code/Magento/Tax/Test/Mftf/composer.json deleted file mode 100644 index e2d9db9f8f76e..0000000000000 --- a/app/code/Magento/Tax/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-tax", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-tax-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Tax/Test/Unit/Model/Plugin/OrderSaveTest.php b/app/code/Magento/Tax/Test/Unit/Model/Plugin/OrderSaveTest.php index 82a473b42a82e..17246df8c5b45 100644 --- a/app/code/Magento/Tax/Test/Unit/Model/Plugin/OrderSaveTest.php +++ b/app/code/Magento/Tax/Test/Unit/Model/Plugin/OrderSaveTest.php @@ -222,93 +222,6 @@ public function testAfterSave( */ public function afterSaveDataProvider() { - $orderTaxDetailsApplied = $this->getMockBuilder(\Magento\Tax\Api\Data\OrderTaxDetailsAppliedTaxInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getRates']) - ->getMockForAbstractClass(); - - $orderTaxDetailsApplied->expects($this->at(0)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 6, - 'code' => 'IL', - 'title' => 'IL', - ], - [ - 'percent' => 5, - 'code' => 'US', - 'title' => 'US', - ] - ] - ); - $orderTaxDetailsApplied->expects($this->at(1)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 3, - 'code' => 'CityTax', - 'title' => 'CityTax', - ], - ] - ); - $orderTaxDetailsApplied->expects($this->at(2)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 6, - 'code' => 'IL', - 'title' => 'IL', - ], - [ - 'percent' => 5, - 'code' => 'US', - 'title' => 'US', - ], - ] - ); - $orderTaxDetailsApplied->expects($this->at(3)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 3, - 'code' => 'CityTax', - 'title' => 'CityTax', - ], - ] - ); - $orderTaxDetailsApplied->expects($this->at(4)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 6, - 'code' => 'IL', - 'title' => 'IL', - ], - [ - 'percent' => 5, - 'code' => 'US', - 'title' => 'US', - ], - ] - ); - $orderTaxDetailsApplied->expects($this->at(5)) - ->method('getRates') - ->willReturn( - [ - [ - 'percent' => 3, - 'code' => 'CityTax', - 'title' => 'CityTax', - ], - ] - ); - return [ //one item with shipping //three tax rates: state and national tax rates of 6 and 5 percent with priority 0 @@ -320,14 +233,35 @@ public function afterSaveDataProvider() 'base_amount' => 0.66, 'percent' => 11, 'id' => 'ILUS', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 6, + 'code' => 'IL', + 'title' => 'IL', + ], + [ + 'percent' => 5, + 'code' => 'US', + 'title' => 'US', + ], + ] + ], ], [ 'amount' => 0.2, 'base_amount' => 0.2, 'percent' => 3.33, 'id' => 'CityTax', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 3, + 'code' => 'CityTax', + 'title' => 'CityTax', + ], + ] + ], ], ], 'item_applied_taxes' => [ @@ -343,7 +277,20 @@ public function afterSaveDataProvider() 'base_amount' => 0.11, 'percent' => 11, 'id' => 'ILUS', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 6, + 'code' => 'IL', + 'title' => 'IL', + ], + [ + 'percent' => 5, + 'code' => 'US', + 'title' => 'US', + ], + ] + ], ], //city tax [ @@ -351,7 +298,15 @@ public function afterSaveDataProvider() 'base_amount' => 0.03, 'percent' => 3.33, 'id' => 'CityTax', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 3, + 'code' => 'CityTax', + 'title' => 'CityTax', + ], + ] + ], ], ], ], @@ -367,7 +322,20 @@ public function afterSaveDataProvider() 'base_amount' => 0.55, 'percent' => 11, 'id' => 'ILUS', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 6, + 'code' => 'IL', + 'title' => 'IL', + ], + [ + 'percent' => 5, + 'code' => 'US', + 'title' => 'US', + ], + ] + ], ], //city tax [ @@ -375,7 +343,15 @@ public function afterSaveDataProvider() 'base_amount' => 0.17, 'percent' => 3.33, 'id' => 'CityTax', - 'extension_attributes' => $orderTaxDetailsApplied, + 'extension_attributes' => [ + 'rates' => [ + [ + 'percent' => 3, + 'code' => 'CityTax', + 'title' => 'CityTax', + ], + ] + ], ], ], ], diff --git a/app/code/Magento/Tax/Test/Unit/Model/TaxClass/Type/CustomerTest.php b/app/code/Magento/Tax/Test/Unit/Model/TaxClass/Type/CustomerTest.php index fc27e68c8e55b..707b999c5e467 100644 --- a/app/code/Magento/Tax/Test/Unit/Model/TaxClass/Type/CustomerTest.php +++ b/app/code/Magento/Tax/Test/Unit/Model/TaxClass/Type/CustomerTest.php @@ -26,9 +26,9 @@ public function testIsAssignedToObjects() $filterBuilder->expects($this->once())->method('setField')->with( \Magento\Customer\Api\Data\GroupInterface::TAX_CLASS_ID - )->willReturnself(); - $filterBuilder->expects($this->once())->method('setValue')->willReturnself(); - $filterBuilder->expects($this->once())->method('create')->willReturnself(); + )->willReturnSelf(); + $filterBuilder->expects($this->once())->method('setValue')->willReturnSelf(); + $filterBuilder->expects($this->once())->method('create')->willReturnSelf(); $filterGroupBuilder = $this->createMock(\Magento\Framework\Api\Search\FilterGroupBuilder::class); $searchCriteriaBuilder = $this->getMockBuilder(\Magento\Framework\Api\SearchCriteriaBuilder::class) diff --git a/app/code/Magento/Tax/Test/Unit/Ui/DataProvider/Product/Listing/Collector/TaxTest.php b/app/code/Magento/Tax/Test/Unit/Ui/DataProvider/Product/Listing/Collector/TaxTest.php index 4cf3f4d339e18..3f80d97ea921b 100644 --- a/app/code/Magento/Tax/Test/Unit/Ui/DataProvider/Product/Listing/Collector/TaxTest.php +++ b/app/code/Magento/Tax/Test/Unit/Ui/DataProvider/Product/Listing/Collector/TaxTest.php @@ -54,6 +54,9 @@ class TaxTest extends \PHPUnit\Framework\TestCase */ private $formattedPriceInfoBuilder; + /** + * @return void + */ protected function setUp() { $this->priceCurrencyMock = $this->getMockBuilder(PriceCurrencyInterface::class) @@ -68,10 +71,12 @@ protected function setUp() ->getMockForAbstractClass(); $this->priceInfoFactory = $this->getMockBuilder(PriceInfoInterfaceFactory::class) + ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $this->priceInfoExtensionFactory = $this->getMockBuilder(PriceInfoExtensionInterfaceFactory::class) + ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $this->formattedPriceInfoBuilder = $this->getMockBuilder(FormattedPriceInfoBuilder::class) @@ -86,6 +91,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testCollect() { $amountValue = 10; diff --git a/app/code/Magento/Tax/etc/adminhtml/system.xml b/app/code/Magento/Tax/etc/adminhtml/system.xml index c03a8aa44bf7b..7fc1744b8e27e 100644 --- a/app/code/Magento/Tax/etc/adminhtml/system.xml +++ b/app/code/Magento/Tax/etc/adminhtml/system.xml @@ -62,7 +62,7 @@ <backend_model>Magento\Tax\Model\Config\Notification</backend_model> <comment>Warning: To apply the discount on prices including tax and apply the tax after discount, set Catalog Prices to “Including Tax”.</comment> </field> - <field id="apply_tax_on" translate="label comment" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <field id="apply_tax_on" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Apply Tax On</label> <source_model>Magento\Tax\Model\Config\Source\Apply\On</source_model> </field> diff --git a/app/code/Magento/Tax/etc/db_schema_whitelist.json b/app/code/Magento/Tax/etc/db_schema_whitelist.json index 4e9731abce738..36a1dee1b455a 100644 --- a/app/code/Magento/Tax/etc/db_schema_whitelist.json +++ b/app/code/Magento/Tax/etc/db_schema_whitelist.json @@ -1,128 +1,128 @@ { - "tax_class": { - "column": { - "class_id": true, - "class_name": true, - "class_type": true + "tax_class": { + "column": { + "class_id": true, + "class_name": true, + "class_type": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "tax_calculation_rule": { - "column": { - "tax_calculation_rule_id": true, - "code": true, - "priority": true, - "position": true, - "calculate_subtotal": true - }, - "index": { - "TAX_CALCULATION_RULE_PRIORITY_POSITION": true, - "TAX_CALCULATION_RULE_CODE": true + "tax_calculation_rule": { + "column": { + "tax_calculation_rule_id": true, + "code": true, + "priority": true, + "position": true, + "calculate_subtotal": true + }, + "index": { + "TAX_CALCULATION_RULE_PRIORITY_POSITION": true, + "TAX_CALCULATION_RULE_CODE": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "tax_calculation_rate": { - "column": { - "tax_calculation_rate_id": true, - "tax_country_id": true, - "tax_region_id": true, - "tax_postcode": true, - "code": true, - "rate": true, - "zip_is_range": true, - "zip_from": true, - "zip_to": true + "tax_calculation_rate": { + "column": { + "tax_calculation_rate_id": true, + "tax_country_id": true, + "tax_region_id": true, + "tax_postcode": true, + "code": true, + "rate": true, + "zip_is_range": true, + "zip_from": true, + "zip_to": true + }, + "index": { + "TAX_CALCULATION_RATE_TAX_COUNTRY_ID_TAX_REGION_ID_TAX_POSTCODE": true, + "TAX_CALCULATION_RATE_CODE": true, + "IDX_CA799F1E2CB843495F601E56C84A626D": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "TAX_CALCULATION_RATE_TAX_COUNTRY_ID_TAX_REGION_ID_TAX_POSTCODE": true, - "TAX_CALCULATION_RATE_CODE": true, - "IDX_CA799F1E2CB843495F601E56C84A626D": true + "tax_calculation": { + "column": { + "tax_calculation_id": true, + "tax_calculation_rate_id": true, + "tax_calculation_rule_id": true, + "customer_tax_class_id": true, + "product_tax_class_id": true + }, + "index": { + "TAX_CALCULATION_TAX_CALCULATION_RULE_ID": true, + "TAX_CALCULATION_CUSTOMER_TAX_CLASS_ID": true, + "TAX_CALCULATION_PRODUCT_TAX_CLASS_ID": true, + "TAX_CALC_TAX_CALC_RATE_ID_CSTR_TAX_CLASS_ID_PRD_TAX_CLASS_ID": true + }, + "constraint": { + "PRIMARY": true, + "TAX_CALCULATION_PRODUCT_TAX_CLASS_ID_TAX_CLASS_CLASS_ID": true, + "TAX_CALCULATION_CUSTOMER_TAX_CLASS_ID_TAX_CLASS_CLASS_ID": true, + "TAX_CALC_TAX_CALC_RATE_ID_TAX_CALC_RATE_TAX_CALC_RATE_ID": true, + "TAX_CALC_TAX_CALC_RULE_ID_TAX_CALC_RULE_TAX_CALC_RULE_ID": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "tax_calculation": { - "column": { - "tax_calculation_id": true, - "tax_calculation_rate_id": true, - "tax_calculation_rule_id": true, - "customer_tax_class_id": true, - "product_tax_class_id": true - }, - "index": { - "TAX_CALCULATION_TAX_CALCULATION_RULE_ID": true, - "TAX_CALCULATION_CUSTOMER_TAX_CLASS_ID": true, - "TAX_CALCULATION_PRODUCT_TAX_CLASS_ID": true, - "TAX_CALC_TAX_CALC_RATE_ID_CSTR_TAX_CLASS_ID_PRD_TAX_CLASS_ID": true - }, - "constraint": { - "PRIMARY": true, - "TAX_CALCULATION_PRODUCT_TAX_CLASS_ID_TAX_CLASS_CLASS_ID": true, - "TAX_CALCULATION_CUSTOMER_TAX_CLASS_ID_TAX_CLASS_CLASS_ID": true, - "TAX_CALC_TAX_CALC_RATE_ID_TAX_CALC_RATE_TAX_CALC_RATE_ID": true, - "TAX_CALC_TAX_CALC_RULE_ID_TAX_CALC_RULE_TAX_CALC_RULE_ID": true - } - }, - "tax_calculation_rate_title": { - "column": { - "tax_calculation_rate_title_id": true, - "tax_calculation_rate_id": true, - "store_id": true, - "value": true - }, - "index": { - "TAX_CALCULATION_RATE_TITLE_TAX_CALCULATION_RATE_ID_STORE_ID": true, - "TAX_CALCULATION_RATE_TITLE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "TAX_CALCULATION_RATE_TITLE_STORE_ID_STORE_STORE_ID": true, - "FK_37FB965F786AD5897BB3AE90470C42AB": true - } - }, - "tax_order_aggregated_created": { - "column": { - "id": true, - "period": true, - "store_id": true, - "code": true, - "order_status": true, - "percent": true, - "orders_count": true, - "tax_base_amount_sum": true - }, - "index": { - "TAX_ORDER_AGGREGATED_CREATED_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "TAX_ORDER_AGGREGATED_CREATED_STORE_ID_STORE_STORE_ID": true, - "TAX_ORDER_AGGRED_CREATED_PERIOD_STORE_ID_CODE_PERCENT_ORDER_STS": true - } - }, - "tax_order_aggregated_updated": { - "column": { - "id": true, - "period": true, - "store_id": true, - "code": true, - "order_status": true, - "percent": true, - "orders_count": true, - "tax_base_amount_sum": true + "tax_calculation_rate_title": { + "column": { + "tax_calculation_rate_title_id": true, + "tax_calculation_rate_id": true, + "store_id": true, + "value": true + }, + "index": { + "TAX_CALCULATION_RATE_TITLE_TAX_CALCULATION_RATE_ID_STORE_ID": true, + "TAX_CALCULATION_RATE_TITLE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "TAX_CALCULATION_RATE_TITLE_STORE_ID_STORE_STORE_ID": true, + "FK_37FB965F786AD5897BB3AE90470C42AB": true + } }, - "index": { - "TAX_ORDER_AGGREGATED_UPDATED_STORE_ID": true + "tax_order_aggregated_created": { + "column": { + "id": true, + "period": true, + "store_id": true, + "code": true, + "order_status": true, + "percent": true, + "orders_count": true, + "tax_base_amount_sum": true + }, + "index": { + "TAX_ORDER_AGGREGATED_CREATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "TAX_ORDER_AGGREGATED_CREATED_STORE_ID_STORE_STORE_ID": true, + "TAX_ORDER_AGGRED_CREATED_PERIOD_STORE_ID_CODE_PERCENT_ORDER_STS": true + } }, - "constraint": { - "PRIMARY": true, - "TAX_ORDER_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, - "TAX_ORDER_AGGRED_UPDATED_PERIOD_STORE_ID_CODE_PERCENT_ORDER_STS": true + "tax_order_aggregated_updated": { + "column": { + "id": true, + "period": true, + "store_id": true, + "code": true, + "order_status": true, + "percent": true, + "orders_count": true, + "tax_base_amount_sum": true + }, + "index": { + "TAX_ORDER_AGGREGATED_UPDATED_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "TAX_ORDER_AGGREGATED_UPDATED_STORE_ID_STORE_STORE_ID": true, + "TAX_ORDER_AGGRED_UPDATED_PERIOD_STORE_ID_CODE_PERCENT_ORDER_STS": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/TaxGraphQl/Test/Mftf/composer.json b/app/code/Magento/TaxGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 144f778833cd4..0000000000000 --- a/app/code/Magento/TaxGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-tax-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExport.php b/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExport.php index a42877b3ecf8a..ab64567f4fe28 100644 --- a/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExport.php +++ b/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExport.php @@ -14,7 +14,7 @@ class ImportExport extends \Magento\Backend\Block\Widget /** * @var string */ - protected $_template = 'importExport.phtml'; + protected $_template = 'Magento_TaxImportExport::importExport.phtml'; /** * @param \Magento\Backend\Block\Template\Context $context diff --git a/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExportHeader.php b/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExportHeader.php index 8897e9b2083e9..e223adc3adb1a 100644 --- a/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExportHeader.php +++ b/app/code/Magento/TaxImportExport/Block/Adminhtml/Rate/ImportExportHeader.php @@ -16,5 +16,5 @@ class ImportExportHeader extends \Magento\Backend\Block\Widget * * @var string */ - protected $_template = 'importExportHeader.phtml'; + protected $_template = 'Magento_TaxImportExport::importExportHeader.phtml'; } diff --git a/app/code/Magento/TaxImportExport/Model/Rate/CsvImportHandler.php b/app/code/Magento/TaxImportExport/Model/Rate/CsvImportHandler.php index 8ec871a182ab4..c8cba30c9cd25 100644 --- a/app/code/Magento/TaxImportExport/Model/Rate/CsvImportHandler.php +++ b/app/code/Magento/TaxImportExport/Model/Rate/CsvImportHandler.php @@ -237,7 +237,7 @@ protected function _importRate(array $rateData, array $regionsCache, array $stor $countryCode = $rateData[1]; $country = $this->_countryFactory->create()->loadByCode($countryCode, 'iso2_code'); if (!$country->getId()) { - throw new \Magento\Framework\Exception\LocalizedException(__('One of the countries has invalid code.')); + throw new \Magento\Framework\Exception\LocalizedException(__('Country code is invalid: %1', $countryCode)); } $regionsCache = $this->_addCountryRegionsToCache($countryCode, $regionsCache); diff --git a/app/code/Magento/TaxImportExport/Test/Mftf/composer.json b/app/code/Magento/TaxImportExport/Test/Mftf/composer.json deleted file mode 100644 index 39bc90df32651..0000000000000 --- a/app/code/Magento/TaxImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-tax-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/TaxImportExport/etc/acl.xml b/app/code/Magento/TaxImportExport/etc/acl.xml index 35c811e8441eb..7a7ac16743225 100644 --- a/app/code/Magento/TaxImportExport/etc/acl.xml +++ b/app/code/Magento/TaxImportExport/etc/acl.xml @@ -11,7 +11,7 @@ <resource id="Magento_Backend::admin"> <resource id="Magento_Backend::system"> <resource id="Magento_Backend::convert"> - <resource id="Magento_TaxImportExport::import_export" title="Import/Export Tax Rates" sortOrder="30" /> + <resource id="Magento_TaxImportExport::import_export" title="Import/Export Tax Rates" translate="title" sortOrder="30" /> </resource> </resource> </resource> diff --git a/app/code/Magento/TaxImportExport/i18n/en_US.csv b/app/code/Magento/TaxImportExport/i18n/en_US.csv index cadecc39a391c..95f94dcfd3b2c 100644 --- a/app/code/Magento/TaxImportExport/i18n/en_US.csv +++ b/app/code/Magento/TaxImportExport/i18n/en_US.csv @@ -12,8 +12,9 @@ Rate,Rate "Invalid file upload attempt","Invalid file upload attempt" "Invalid file upload attempt.","Invalid file upload attempt." "Invalid file format.","Invalid file format." -"One of the countries has invalid code.","One of the countries has invalid code." +"Country code is invalid: %1","Country code is invalid: %1" "Import Tax Rates","Import Tax Rates" "Export Tax Rates","Export Tax Rates" CSV,CSV "Excel XML","Excel XML" +"Import/Export Tax Rates","Import/Export Tax Rates" diff --git a/app/code/Magento/Theme/Block/Adminhtml/Wysiwyg/Files/Content/Uploader.php b/app/code/Magento/Theme/Block/Adminhtml/Wysiwyg/Files/Content/Uploader.php index 8e7f4c9cc680c..e99500dbd0694 100644 --- a/app/code/Magento/Theme/Block/Adminhtml/Wysiwyg/Files/Content/Uploader.php +++ b/app/code/Magento/Theme/Block/Adminhtml/Wysiwyg/Files/Content/Uploader.php @@ -19,7 +19,7 @@ class Uploader extends \Magento\Backend\Block\Media\Uploader * * @var string */ - protected $_template = 'browser/content/uploader.phtml'; + protected $_template = 'Magento_Theme::browser/content/uploader.phtml'; /** * @var \Magento\Theme\Helper\Storage diff --git a/app/code/Magento/Theme/Block/Html/Breadcrumbs.php b/app/code/Magento/Theme/Block/Html/Breadcrumbs.php index c1f8ea620ef41..cff87fc8726bd 100644 --- a/app/code/Magento/Theme/Block/Html/Breadcrumbs.php +++ b/app/code/Magento/Theme/Block/Html/Breadcrumbs.php @@ -21,7 +21,7 @@ class Breadcrumbs extends \Magento\Framework\View\Element\Template * * @var string */ - protected $_template = 'html/breadcrumbs.phtml'; + protected $_template = 'Magento_Theme::html/breadcrumbs.phtml'; /** * List of available breadcrumb properties diff --git a/app/code/Magento/Theme/Block/Html/Header.php b/app/code/Magento/Theme/Block/Html/Header.php index f597b4034da92..2663a4da15011 100644 --- a/app/code/Magento/Theme/Block/Html/Header.php +++ b/app/code/Magento/Theme/Block/Html/Header.php @@ -19,7 +19,7 @@ class Header extends \Magento\Framework\View\Element\Template * * @var string */ - protected $_template = 'html/header.phtml'; + protected $_template = 'Magento_Theme::html/header.phtml'; /** * Retrieve welcome text diff --git a/app/code/Magento/Theme/Block/Html/Header/Logo.php b/app/code/Magento/Theme/Block/Html/Header/Logo.php index 5b0c2eaf04c45..0a0e71f44ba32 100644 --- a/app/code/Magento/Theme/Block/Html/Header/Logo.php +++ b/app/code/Magento/Theme/Block/Html/Header/Logo.php @@ -19,7 +19,7 @@ class Logo extends \Magento\Framework\View\Element\Template * * @var string */ - protected $_template = 'html/header/logo.phtml'; + protected $_template = 'Magento_Theme::html/header/logo.phtml'; /** * @var \Magento\MediaStorage\Helper\File\Storage\Database diff --git a/app/code/Magento/Theme/Controller/Result/MessagePlugin.php b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php index 145493b8e44d8..83172df748a47 100644 --- a/app/code/Magento/Theme/Controller/Result/MessagePlugin.php +++ b/app/code/Magento/Theme/Controller/Result/MessagePlugin.php @@ -5,9 +5,12 @@ */ namespace Magento\Theme\Controller\Result; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Controller\Result\Json; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Translate\Inline\ParserInterface; +use Magento\Framework\Translate\InlineInterface; /** * Plugin for putting messages to cookies @@ -44,26 +47,34 @@ class MessagePlugin */ private $serializer; + /** + * @var InlineInterface + */ + private $inlineTranslate; + /** * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory * @param \Magento\Framework\Message\ManagerInterface $messageManager * @param \Magento\Framework\View\Element\Message\InterpretationStrategyInterface $interpretationStrategy * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + * @param InlineInterface|null $inlineTranslate */ public function __construct( \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager, \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory, \Magento\Framework\Message\ManagerInterface $messageManager, \Magento\Framework\View\Element\Message\InterpretationStrategyInterface $interpretationStrategy, - \Magento\Framework\Serialize\Serializer\Json $serializer = null + \Magento\Framework\Serialize\Serializer\Json $serializer = null, + InlineInterface $inlineTranslate = null ) { $this->cookieManager = $cookieManager; $this->cookieMetadataFactory = $cookieMetadataFactory; $this->messageManager = $messageManager; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->serializer = $serializer ?: ObjectManager::getInstance() ->get(\Magento\Framework\Serialize\Serializer\Json::class); $this->interpretationStrategy = $interpretationStrategy; + $this->inlineTranslate = $inlineTranslate ?: ObjectManager::getInstance()->get(InlineInterface::class); } /** @@ -112,6 +123,12 @@ public function afterRenderResult( private function setCookie(array $messages) { if (!empty($messages)) { + if ($this->inlineTranslate->isAllowed()) { + foreach ($messages as &$message) { + $message['text'] = $this->convertMessageText($message['text']); + } + } + $publicCookieMetadata = $this->cookieMetadataFactory->createPublicCookieMetadata(); $publicCookieMetadata->setDurationOneYear(); $publicCookieMetadata->setPath('/'); @@ -125,6 +142,21 @@ private function setCookie(array $messages) } } + /** + * Replace wrapping translation with html body. + * + * @param string $text + * @return string + */ + private function convertMessageText(string $text): string + { + if (preg_match('#' . ParserInterface::REGEXP_TOKEN . '#', $text, $matches)) { + $text = $matches[1]; + } + + return $text; + } + /** * Return messages array and clean message manager messages * diff --git a/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml b/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml new file mode 100644 index 0000000000000..ec28e8ed7a999 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="Layout" type="page_layout"> + <data key="1column">1 column</data> + </entity> +</entities> diff --git a/app/code/Magento/Theme/Test/Mftf/Page/ThemesPage.xml b/app/code/Magento/Theme/Test/Mftf/Page/ThemesPage.xml index e0f0d2db70602..ab7b436cb3ae3 100644 --- a/app/code/Magento/Theme/Test/Mftf/Page/ThemesPage.xml +++ b/app/code/Magento/Theme/Test/Mftf/Page/ThemesPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="ThemesPageIndex" url="admin/system_design_theme/" area="admin" module="Magento_Theme"> <section name="AdminThemeSection"/> </page> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/AdminThemeSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/AdminThemeSection.xml index 2e5a786d45725..219ca7420361c 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/AdminThemeSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/AdminThemeSection.xml @@ -7,10 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminThemeSection"> <!--All rows in a specific Column e.g. {{Section.rowsInColumn('columnName')}}--> - <element name="rowsInColumn" type="text" selector="//tr/td[contains(@class, '{{column}}')]" parameterized="true"/> + <element name="allRowsInColumn" type="text" selector="//tr/td[contains(@class, '{{column}}')]" parameterized="true"/> <!--selector for Theme Title column since it needs to be handled separately--> <element name="rowsInThemeTitleColumn" type="text" selector="//tbody/tr/td[contains(@class, 'parent_theme')]/preceding-sibling::td"/> <element name="rowsInColumn" type="text" selector="//tbody/tr/td[contains(@class, '{{column}}')]" parameterized="true"/> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontFooterSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontFooterSection.xml index a9db1cbb9c776..d9854b7a80b9b 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontFooterSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontFooterSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontFooterSection"> </section> </sections> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontMessagesSection.xml index 9b0b9c7a053bc..a7bc36f1e629b 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontMessagesSection"> <element name="message" type="text" selector="//main//div[contains(@class, 'messages')]//div[contains(@class, 'message')]/div[contains(text(), '{{var1}}')]" diff --git a/app/code/Magento/Theme/Test/Mftf/Test/ThemeTest.xml b/app/code/Magento/Theme/Test/Mftf/Test/ThemeTest.xml index 2ae24934a48df..67213934a6c41 100644 --- a/app/code/Magento/Theme/Test/Mftf/Test/ThemeTest.xml +++ b/app/code/Magento/Theme/Test/Mftf/Test/ThemeTest.xml @@ -7,15 +7,16 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ThemeTest"> <annotations> - <features value="Theme Test"/> + <features value="Theme"/> + <stories value="Themes"/> <title value="Magento Rush theme should not be available in Themes grid"/> <description value="Magento Rush theme should not be available in Themes grid"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-91409"/> - <group value="theme"/> + <group value="Theme"/> </annotations> <after> <actionGroup ref="logout" stepKey="logoutOfAdmin"/> diff --git a/app/code/Magento/Theme/Test/Mftf/composer.json b/app/code/Magento/Theme/Test/Mftf/composer.json deleted file mode 100644 index f93dac15279a4..0000000000000 --- a/app/code/Magento/Theme/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-theme", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-translation": "100.0.0-dev", - "magento/functional-test-module-theme-sample-data": "100.0.0-dev", - "magento/functional-test-module-deploy": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/UploadJsTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/UploadJsTest.php index d5919db5edcba..bbcaa87acb9c3 100644 --- a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/UploadJsTest.php +++ b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/System/Design/Theme/UploadJsTest.php @@ -63,22 +63,22 @@ public function testExecuteWithoutTheme() ->expects($this->at(0)) ->method('get') ->with(\Magento\Theme\Model\Uploader\Service::class) - ->WillReturn($this->serviceModel); + ->willReturn($this->serviceModel); $this->_objectManagerMock ->expects($this->at(1)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\FlyweightFactory::class) - ->WillReturn($this->themeFactory); + ->willReturn($this->themeFactory); $this->_objectManagerMock ->expects($this->at(2)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\Customization\File\Js::class) - ->WillReturn($this->customizationJs); + ->willReturn($this->customizationJs); $this->_objectManagerMock ->expects($this->at(3)) ->method('get') ->with(\Magento\Framework\Json\Helper\Data::class) - ->WillReturn($this->jsonHelper); + ->willReturn($this->jsonHelper); $this->themeFactory->expects($this->once()) ->method('create') @@ -107,21 +107,21 @@ public function testExecuteWithException() $this->_objectManagerMock->expects($this->at(0)) ->method('get') ->with(\Magento\Theme\Model\Uploader\Service::class) - ->WillReturn($this->serviceModel); + ->willReturn($this->serviceModel); $this->_objectManagerMock->expects($this->at(1)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\FlyweightFactory::class) - ->WillReturn($this->themeFactory); + ->willReturn($this->themeFactory); $this->_objectManagerMock ->expects($this->at(2)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\Customization\File\Js::class) - ->WillReturn($this->customizationJs); + ->willReturn($this->customizationJs); $this->_objectManagerMock ->expects($this->at(4)) ->method('get') ->with(\Magento\Framework\Json\Helper\Data::class) - ->WillReturn($this->jsonHelper); + ->willReturn($this->jsonHelper); $this->themeFactory->expects($this->once()) ->method('create') @@ -172,19 +172,19 @@ public function testExecute() $this->_objectManagerMock->expects($this->at(0)) ->method('get') ->with(\Magento\Theme\Model\Uploader\Service::class) - ->WillReturn($this->serviceModel); + ->willReturn($this->serviceModel); $this->_objectManagerMock->expects($this->at(1)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\FlyweightFactory::class) - ->WillReturn($this->themeFactory); + ->willReturn($this->themeFactory); $this->_objectManagerMock->expects($this->at(2)) ->method('get') ->with(\Magento\Framework\View\Design\Theme\Customization\File\Js::class) - ->WillReturn($this->customizationJs); + ->willReturn($this->customizationJs); $this->_objectManagerMock->expects($this->at(4)) ->method('get') ->with(\Magento\Framework\Json\Helper\Data::class) - ->WillReturn($this->jsonHelper); + ->willReturn($this->jsonHelper); $this->themeFactory->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php index 60457fc1436c0..748f7a1fcb9fb 100644 --- a/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php +++ b/app/code/Magento/Theme/Test/Unit/Controller/Result/MessagePluginTest.php @@ -14,6 +14,7 @@ use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory; use Magento\Framework\Stdlib\Cookie\PublicCookieMetadata; use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\Framework\Translate\InlineInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Theme\Controller\Result\MessagePlugin; @@ -40,6 +41,9 @@ class MessagePluginTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ private $serializerMock; + /** @var InlineInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $inlineTranslateMock; + protected function setUp() { $this->cookieManagerMock = $this->getMockBuilder(CookieManagerInterface::class) @@ -53,13 +57,15 @@ protected function setUp() ->getMockForAbstractClass(); $this->serializerMock = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) ->getMock(); + $this->inlineTranslateMock = $this->getMockBuilder(InlineInterface::class)->getMockForAbstractClass(); $this->model = new MessagePlugin( $this->cookieManagerMock, $this->cookieMetadataFactoryMock, $this->managerMock, $this->interpretationStrategyMock, - $this->serializerMock + $this->serializerMock, + $this->inlineTranslateMock ); } @@ -450,4 +456,93 @@ function ($data) { $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); } + + /** + * @return void + */ + public function testAfterRenderResultWithAllowedInlineTranslate(): void + { + $messageType = 'message1type'; + $messageText = '{{{message1text}}{{message1text}}{{message1text}}{{theme/luma}}}'; + $expectedMessages = [ + [ + 'type' => $messageType, + 'text' => 'message1text', + ], + ]; + + /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $resultMock */ + $resultMock = $this->getMockBuilder(Redirect::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var PublicCookieMetadata|\PHPUnit_Framework_MockObject_MockObject $cookieMetadataMock */ + $cookieMetadataMock = $this->getMockBuilder(PublicCookieMetadata::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->cookieMetadataFactoryMock->expects($this->once()) + ->method('createPublicCookieMetadata') + ->willReturn($cookieMetadataMock); + + $this->cookieManagerMock->expects($this->once()) + ->method('setPublicCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME, + json_encode($expectedMessages), + $cookieMetadataMock + ); + $this->cookieManagerMock->expects($this->once()) + ->method('getCookie') + ->with( + MessagePlugin::MESSAGES_COOKIES_NAME + ) + ->willReturn(json_encode([])); + + $this->serializerMock->expects($this->once()) + ->method('unserialize') + ->willReturnCallback( + function ($data) { + return json_decode($data, true); + } + ); + $this->serializerMock->expects($this->once()) + ->method('serialize') + ->willReturnCallback( + function ($data) { + return json_encode($data); + } + ); + + /** @var MessageInterface|\PHPUnit_Framework_MockObject_MockObject $messageMock */ + $messageMock = $this->getMockBuilder(MessageInterface::class) + ->getMock(); + $messageMock->expects($this->once()) + ->method('getType') + ->willReturn($messageType); + + $this->interpretationStrategyMock->expects($this->once()) + ->method('interpret') + ->with($messageMock) + ->willReturn($messageText); + + $this->inlineTranslateMock->expects($this->once()) + ->method('isAllowed') + ->willReturn(true); + + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $collectionMock->expects($this->once()) + ->method('getItems') + ->willReturn([$messageMock]); + + $this->managerMock->expects($this->once()) + ->method('getMessages') + ->with(true, null) + ->willReturn($collectionMock); + + $this->assertEquals($resultMock, $this->model->afterRenderResult($resultMock, $resultMock)); + } } diff --git a/app/code/Magento/Theme/etc/db_schema_whitelist.json b/app/code/Magento/Theme/etc/db_schema_whitelist.json index 2d1c3f8b6554a..25b81c16bfcab 100644 --- a/app/code/Magento/Theme/etc/db_schema_whitelist.json +++ b/app/code/Magento/Theme/etc/db_schema_whitelist.json @@ -1,49 +1,49 @@ { - "theme": { - "column": { - "theme_id": true, - "parent_id": true, - "theme_path": true, - "theme_title": true, - "preview_image": true, - "is_featured": true, - "area": true, - "type": true, - "code": true + "theme": { + "column": { + "theme_id": true, + "parent_id": true, + "theme_path": true, + "theme_title": true, + "preview_image": true, + "is_featured": true, + "area": true, + "type": true, + "code": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "theme_file": { - "column": { - "theme_files_id": true, - "theme_id": true, - "file_path": true, - "file_type": true, - "content": true, - "sort_order": true, - "is_temporary": true - }, - "constraint": { - "PRIMARY": true, - "THEME_FILE_THEME_ID_THEME_THEME_ID": true - } - }, - "design_change": { - "column": { - "design_change_id": true, - "store_id": true, - "design": true, - "date_from": true, - "date_to": true - }, - "index": { - "DESIGN_CHANGE_STORE_ID": true + "theme_file": { + "column": { + "theme_files_id": true, + "theme_id": true, + "file_path": true, + "file_type": true, + "content": true, + "sort_order": true, + "is_temporary": true + }, + "constraint": { + "PRIMARY": true, + "THEME_FILE_THEME_ID_THEME_THEME_ID": true + } }, - "constraint": { - "PRIMARY": true, - "DESIGN_CHANGE_STORE_ID_STORE_STORE_ID": true + "design_change": { + "column": { + "design_change_id": true, + "store_id": true, + "design": true, + "date_from": true, + "date_to": true + }, + "index": { + "DESIGN_CHANGE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "DESIGN_CHANGE_STORE_ID_STORE_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index 55119f3449389..5ff82ce2db6f6 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -69,7 +69,7 @@ </type> <type name="Magento\Framework\App\Area"> <arguments> - <argument name="translator" xsi:type="object">Magento\Framework\Translate</argument> + <argument name="translator" xsi:type="object">Magento\Framework\TranslateInterface</argument> <argument name="design" xsi:type="object">Magento\Theme\Model\Design\Proxy</argument> </arguments> </type> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/bugreport.phtml b/app/code/Magento/Theme/view/frontend/templates/html/bugreport.phtml index 5d8203244256e..f147b7085945f 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/bugreport.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/bugreport.phtml @@ -7,7 +7,7 @@ <small class="bugs"> <span><?= /* @escapeNotVerified */ __('Help Us Keep Magento Healthy') ?></span> <a href="http://www.magentocommerce.com/bug-tracking" - target="_blank"> + target="_blank" title="<?= /* @escapeNotVerified */ __('Report All Bugs') ?>"> <?= /* @escapeNotVerified */ __('Report All Bugs') ?> </a> </small> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/collapsible.phtml b/app/code/Magento/Theme/view/frontend/templates/html/collapsible.phtml index 318bcc8f2cc7c..c3ad4a89399a0 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/collapsible.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/collapsible.phtml @@ -10,7 +10,7 @@ <div class="block <?= /* @escapeNotVerified */ $block->getBlockCss() ?>"> <div class="title <?= /* @escapeNotVerified */ $block->getBlockCss() ?>-title" data-mage-init='{"toggleAdvanced": {"toggleContainers": "#<?= /* @escapeNotVerified */ $block->getBlockCss() ?>", "selectorsToggleClass": "active"}}'> - <strong><?= /* @escapeNotVerified */ $block->getBlockTitle() ?></strong> + <strong><?= /* @escapeNotVerified */ __($block->getBlockTitle()) ?></strong> </div> <div class="content <?= /* @escapeNotVerified */ $block->getBlockCss() ?>-content" id="<?= /* @escapeNotVerified */ $block->getBlockCss() ?>"> <?= $block->getChildHtml() ?> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml index 8c3663337cbbe..17f8d7c70f574 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml @@ -18,6 +18,7 @@ <a class="logo" href="<?= $block->getUrl('') ?>" title="<?= /* @escapeNotVerified */ $storeName ?>"> <?php endif ?> <img src="<?= /* @escapeNotVerified */ $block->getLogoSrc() ?>" + title="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" alt="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" <?= $block->getLogoWidth() ? 'width="' . $block->getLogoWidth() . '"' : '' ?> <?= $block->getLogoHeight() ? 'height="' . $block->getLogoHeight() . '"' : '' ?> diff --git a/app/code/Magento/Theme/view/frontend/web/templates/breadcrumbs.html b/app/code/Magento/Theme/view/frontend/web/templates/breadcrumbs.html index f298c0a58e814..792eb2e636005 100644 --- a/app/code/Magento/Theme/view/frontend/web/templates/breadcrumbs.html +++ b/app/code/Magento/Theme/view/frontend/web/templates/breadcrumbs.html @@ -10,9 +10,9 @@ <% if (crumb.link) { %> <a href="<%= crumb.link %>" title="<%- crumb.title %>"><%- crumb.label %></a> <% } else if (crumb.last) { %> - <strong><%- crumb.label %></strong> + <strong><%= crumb.label %></strong> <% } else { %> - <%- crumb.label %> + <%= crumb.label %> <% } %> </li> <% }); %> diff --git a/app/code/Magento/Tinymce3/Test/Mftf/Section/AdminTinymce3FileldsSection.xml b/app/code/Magento/Tinymce3/Test/Mftf/Section/AdminTinymce3FileldsSection.xml index 83296abf61dc2..1730996937ca2 100644 --- a/app/code/Magento/Tinymce3/Test/Mftf/Section/AdminTinymce3FileldsSection.xml +++ b/app/code/Magento/Tinymce3/Test/Mftf/Section/AdminTinymce3FileldsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ProductWYSIWYGSection"> <element name="Tinymce3MSG" type="button" selector=".admin__field-error"/> </section> diff --git a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml index 4a1c7e07c38c6..ed3eea30c8b45 100644 --- a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml +++ b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminSwitchWYSIWYGOptionsTest"> <annotations> <features value="Cms"/> @@ -17,8 +17,9 @@ <description value="Admin should able to switch between versions of TinyMCE"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-82936"/> - <!--Skip because of issue MAGETWO-89417--> - <group value="skip"/> + <skip> + <issueId value="MAGETWO-89417"/> + </skip> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="loginGetFromGeneralFile"/> diff --git a/app/code/Magento/Tinymce3/Test/Mftf/composer.json b/app/code/Magento/Tinymce3/Test/Mftf/composer.json deleted file mode 100644 index 84c1f9376bf45..0000000000000 --- a/app/code/Magento/Tinymce3/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-tinymce-3", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cms": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/ControlManager.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/ControlManager.js index 51fa311525862..784042988c0ef 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/ControlManager.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/ControlManager.js @@ -45,7 +45,7 @@ }, /** - * Returns a control by id or undefined it it wasn't found. + * Returns a control by id or undefined it wasn't found. * * @method get * @param {String} id Control instance name. diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Styles.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Styles.js index edf1e13a4e3bc..60c5565d72a99 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Styles.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/html/Styles.js @@ -79,7 +79,7 @@ tinymce.html.Styles = function(settings, schema) { function compress(prefix, suffix) { var top, right, bottom, left; - // Get values and check it it needs compressing + // Get values and check it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) return; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/autosave/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/autosave/editor_plugin_src.js index 31bcb419fb178..c147e4d8f87d5 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/autosave/editor_plugin_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/autosave/editor_plugin_src.js @@ -20,14 +20,14 @@ * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain * on the client computer. Data stored in the localStorage area has no expiration date, so we must * manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed - * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As + * to be working in Firefox 3 and Safari 3.2, but in reality it is flaky in those browsers. As * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7, * localStorage is stored in the following folder: * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder] * * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage, * except it is designed to expire after a certain amount of time. Because the specification - * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and + * around expiration date/time is very loosely-described, it is preferable to use locaStorage and * manage the expiration ourselves. sessionStorage has similar storage characteristics to * localStorage, although it seems to have better support by Firefox 3 at the moment. (That will * certainly change as Firefox continues getting better at HTML 5 adoption.) @@ -297,7 +297,7 @@ }, /** - * This method will store the current contents in the the storage engine. + * This method will store the current contents in the storage engine. * * @method storeDraft */ diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/paste/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/paste/editor_plugin_src.js index cec4abf98727a..db89eccc4e7ef 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/paste/editor_plugin_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/paste/editor_plugin_src.js @@ -207,7 +207,7 @@ sel.setRng(oldRng); sel.setContent(''); - // For some odd reason we need to detach the the mceInsertContent call from the paste event + // For some odd reason we need to detach the mceInsertContent call from the paste event // It's like IE has a reference to the parent element that you paste in and the selection gets messed up // when it tries to restore the selection setTimeout(function() { diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js index 2daf1620a918e..b3d77f6ce3139 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js @@ -1731,7 +1731,7 @@ tinymce.html.Styles = function(settings, schema) { function compress(prefix, suffix) { var top, right, bottom, left; - // Get values and check it it needs compressing + // Get values and check it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) return; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js index 44b3010b0adfd..daf2ad4e71bac 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js @@ -1483,7 +1483,7 @@ tinymce.html.Styles = function(settings, schema) { function compress(prefix, suffix) { var top, right, bottom, left; - // Get values and check it it needs compressing + // Get values and check it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) return; diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js index 46ba27e60f419..2634633d8eee5 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js @@ -1456,7 +1456,7 @@ tinymce.html.Styles = function(settings, schema) { function compress(prefix, suffix) { var top, right, bottom, left; - // Get values and check it it needs compressing + // Get values and check it needs compressing top = styles[prefix + '-top' + suffix]; if (!top) return; diff --git a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js index 88e2d4cc78f96..7c587c1c8768a 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js +++ b/app/code/Magento/Tinymce3/view/base/web/tinymce3Adapter.js @@ -495,7 +495,7 @@ define([ */ encodeDirectives: function (content) { // collect all HTML tags with attributes that contain directives - return content.gsub(/<([a-z0-9\-\_]+[^>]+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".*?)>/i, function (match) { + return content.gsub(/<([a-z0-9\-\_]+[^>]+?)([a-z0-9\-\_]+="[^"]*?\{\{.+?\}\}.*?".*?)>/i, function (match) { var attributesString = match[2], decodedDirectiveString; diff --git a/app/code/Magento/Translation/Model/Js/DataProvider.php b/app/code/Magento/Translation/Model/Js/DataProvider.php index a73d4ee99b5d6..7aad7c765bcd5 100644 --- a/app/code/Magento/Translation/Model/Js/DataProvider.php +++ b/app/code/Magento/Translation/Model/Js/DataProvider.php @@ -113,7 +113,8 @@ public function getData($themePath) } } catch (\Exception $e) { throw new LocalizedException( - __('Error while translating phrase "%s" in file %s.', $phrase, $filePath[0]) + __('Error while translating phrase "%s" in file %s.', $phrase, $filePath[0]), + $e ); } } diff --git a/app/code/Magento/Translation/Test/Mftf/composer.json b/app/code/Magento/Translation/Test/Mftf/composer.json deleted file mode 100644 index 36bfa66adda0f..0000000000000 --- a/app/code/Magento/Translation/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-translation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-developer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-deploy": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Translation/etc/adminhtml/system.xml b/app/code/Magento/Translation/etc/adminhtml/system.xml index 8c3cdc5c39916..ab854f8a4db52 100644 --- a/app/code/Magento/Translation/etc/adminhtml/system.xml +++ b/app/code/Magento/Translation/etc/adminhtml/system.xml @@ -9,7 +9,7 @@ <system> <section id="dev"> <group id="js"> - <field id="translate_strategy" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="translate_strategy" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Translation Strategy</label> <source_model>Magento\Translation\Model\Js\Config\Source\Strategy</source_model> <comment>Please put your store into maintenance mode and redeploy static files after changing strategy</comment> diff --git a/app/code/Magento/Translation/etc/db_schema_whitelist.json b/app/code/Magento/Translation/etc/db_schema_whitelist.json index deee0d900e3ee..975b1af9bcbbc 100644 --- a/app/code/Magento/Translation/etc/db_schema_whitelist.json +++ b/app/code/Magento/Translation/etc/db_schema_whitelist.json @@ -1,17 +1,17 @@ { - "translation": { - "column": { - "key_id": true, - "string": true, - "store_id": true, - "translate": true, - "locale": true, - "crc_string": true - }, - "constraint": { - "PRIMARY": true, - "TRANSLATION_STORE_ID_STORE_STORE_ID": true, - "TRANSLATION_STORE_ID_LOCALE_CRC_STRING_STRING": true + "translation": { + "column": { + "key_id": true, + "string": true, + "store_id": true, + "translate": true, + "locale": true, + "crc_string": true + }, + "constraint": { + "PRIMARY": true, + "TRANSLATION_STORE_ID_STORE_STORE_ID": true, + "TRANSLATION_STORE_ID_LOCALE_CRC_STRING_STRING": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Translation/etc/di.xml b/app/code/Magento/Translation/etc/di.xml index c10619fbf74ae..6d3ca03953cf9 100644 --- a/app/code/Magento/Translation/etc/di.xml +++ b/app/code/Magento/Translation/etc/di.xml @@ -67,6 +67,7 @@ <item name="translate_wrapping" xsi:type="string"><![CDATA[~translate\=("')([^\'].*?)\'\"~]]></item> <item name="mage_translation_widget" xsi:type="string"><![CDATA[~(?:\$|jQuery)\.mage\.__\((?s)[^'"]*?(['"])(.+?)(?<!\\)\1(?s).*?\)~]]></item> <item name="mage_translation_static" xsi:type="string"><![CDATA[~\$t\((?s)[^'"]*?(["'])(.+?)\1(?s).*?\)~]]></item> + <item name="translate_args" xsi:type="string"><![CDATA[~translate args\=("|'|"')([^\'].*?)('"|'|")~]]></item> </argument> </arguments> </type> diff --git a/app/code/Magento/Ui/Component/Filters/Type/Input.php b/app/code/Magento/Ui/Component/Filters/Type/Input.php index 9cc060ae58172..1fc132700c8a9 100644 --- a/app/code/Magento/Ui/Component/Filters/Type/Input.php +++ b/app/code/Magento/Ui/Component/Filters/Type/Input.php @@ -29,7 +29,7 @@ class Input extends AbstractFilter * * @return void */ - public function prepare() + public function prepare(): void { $this->wrappedComponent = $this->uiComponentFactory->create( $this->getName(), @@ -62,12 +62,12 @@ public function prepare() * * @return void */ - protected function applyFilter() + protected function applyFilter(): void { if (isset($this->filterData[$this->getName()])) { $value = str_replace(['%', '_'], ['\%', '\_'], $this->filterData[$this->getName()]); - if (!empty($value)) { + if ($value || $value === '0') { $filter = $this->filterBuilder->setConditionType('like') ->setField($this->getName()) ->setValue(sprintf('%%%s%%', $value)) diff --git a/app/code/Magento/Ui/Component/MassAction/Filter.php b/app/code/Magento/Ui/Component/MassAction/Filter.php index 6877303f5c491..a8ed5d901d860 100644 --- a/app/code/Magento/Ui/Component/MassAction/Filter.php +++ b/app/code/Magento/Ui/Component/MassAction/Filter.php @@ -99,14 +99,12 @@ public function getCollection(AbstractDb $collection) throw new LocalizedException(__('An item needs to be selected. Select and try again.')); } } - /** @var \Magento\Customer\Model\ResourceModel\Customer\Collection $collection */ - $idsArray = $this->getFilterIds(); - if (!empty($idsArray)) { - $collection->addFieldToFilter( - $collection->getIdFieldName(), - ['in' => $idsArray] - ); - } + + $collection->addFieldToFilter( + $collection->getIdFieldName(), + ['in' => $this->getFilterIds()] + ); + return $collection; } diff --git a/app/code/Magento/Ui/DataProvider/EavValidationRules.php b/app/code/Magento/Ui/DataProvider/EavValidationRules.php index 12e345e1fa12c..6e2bb3866e947 100644 --- a/app/code/Magento/Ui/DataProvider/EavValidationRules.php +++ b/app/code/Magento/Ui/DataProvider/EavValidationRules.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Ui\DataProvider; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; @@ -31,25 +32,51 @@ class EavValidationRules */ public function build(AbstractAttribute $attribute, array $data) { - $validation = []; + $validations = []; if (isset($data['required']) && $data['required'] == 1) { - $validation = array_merge($validation, ['required-entry' => true]); + $validations = array_merge($validations, ['required-entry' => true]); } if ($attribute->getFrontendInput() === 'price') { - $validation = array_merge($validation, ['validate-zero-or-greater' => true]); + $validations = array_merge($validations, ['validate-zero-or-greater' => true]); } if ($attribute->getValidateRules()) { - $validation = array_merge($validation, $attribute->getValidateRules()); + $validations = array_merge($validations, $this->clipLengthRules($attribute->getValidateRules())); } + return $this->aggregateRules($validations); + } + + /** + * @param array $validations + * @return array + */ + private function aggregateRules(array $validations): array + { $rules = []; - foreach ($validation as $type => $ruleName) { - $rule = [$type => $ruleName]; + foreach ($validations as $type => $ruleValue) { + $rule = [$type => $ruleValue]; if ($type === 'input_validation') { - $rule = isset($this->validationRules[$ruleName]) ? $this->validationRules[$ruleName] : []; + $rule = $this->validationRules[$ruleValue] ?? []; + } + if (count($rule) !== 0) { + $key = key($rule); + $rules[$key] = $rule[$key]; } - $rules = array_merge($rules, $rule); } + return $rules; + } + /** + * @param array $rules + * @return array + */ + private function clipLengthRules(array $rules): array + { + if (empty($rules['input_validation'])) { + unset( + $rules['min_text_length'], + $rules['max_text_length'] + ); + } return $rules; } } diff --git a/app/code/Magento/Ui/Model/Export/MetadataProvider.php b/app/code/Magento/Ui/Model/Export/MetadataProvider.php index 499cd06e505f6..54d856e8a6104 100644 --- a/app/code/Magento/Ui/Model/Export/MetadataProvider.php +++ b/app/code/Magento/Ui/Model/Export/MetadataProvider.php @@ -118,6 +118,13 @@ public function getHeaders(UiComponentInterface $component) foreach ($this->getColumns($component) as $column) { $row[] = $column->getData('config/label'); } + + array_walk($row, function (&$header) { + if (mb_strpos($header, 'ID') === 0) { + $header = '"' . $header . '"'; + } + }); + return $row; } diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml index 127647e6e9b48..1942c02ace51b 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Search grid with keyword search--> <actionGroup name="searchAdminDataGridByKeyword"> <arguments> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml index d05a7898872a0..9148c22976c19 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml @@ -7,10 +7,10 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="adminDataGridSelectPerPage"> <arguments> - <argument name="perPage"/> + <argument name="perPage" type="string"/> </arguments> <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown"/> <click selector="{{AdminDataGridPaginationSection.perPageOption(perPage)}}" stepKey="selectCustomPerPage"/> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml index 41de4141b4c58..73d441dd96d1e 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminGridFilterSearchResultsActionGroup.xml @@ -7,14 +7,14 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminGridFilterSearchResultsByInput"> <arguments> <argument name="selector"/> - <argument name="value"/> + <argument name="value" type="string"/> </arguments> - <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-filters-current._show" visible="true" stepKey="clearTheFiltersIfPresent"/> + <conditionalClick selector="{{AdminGridFilterControls.clearAll}}" dependentSelector="(//*[contains(@class, 'admin__data-grid-header')][contains(@data-bind, 'afterRender: \$data.setToolbarNode')]//*[contains(@class, 'admin__data-grid-filters-current')][contains(@class, '_show')])[1]" visible="true" stepKey="clearTheFiltersIfPresent"/> <waitForPageLoad stepKey="waitForPageLoad" time="5"/> <click selector="{{AdminGridFilterControls.filters}}" stepKey="clickOnFilters1"/> diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml index 133b81a5d6044..9a9458ab34d2b 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminSaveAndCloseActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminFormSaveAndClose"> <click selector="{{AdminProductFormActionSection.saveArrow}}" stepKey="openSaveDropDown"/> <click selector="{{AdminProductFormActionSection.saveAndClose}}" stepKey="clickOnSaveAndClose"/> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml index e5766300b0e87..3e917a5944f95 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDataGridHeaderSection"> <!--Search by keyword element--> <element name="search" type="input" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] input[placeholder='Search by keyword']"/> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml index 5e4fc5298bba8..0f54f51549e7a 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml @@ -7,10 +7,10 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDataGridPaginationSection"> <element name="perPageDropdown" type="select" selector=".admin__data-grid-pager-wrap .selectmenu"/> - <element name="perPageOption" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//button[text()='{{label}}']" parameterized="true"/> + <element name="perPageOption" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//button[text()='{{var1}}']" parameterized="true"/> <element name="perPageInput" type="input" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//input"/> <element name="perPageApplyInput" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//button"/> <element name="nextPage" type="button" selector="div.admin__data-grid-pager > button.action-next" timeout="30"/> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml index 2164af26e5ac4..edcc70a82396d 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml @@ -7,11 +7,12 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDataGridTableSection"> <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> <element name="column" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{col}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> + <element name="rowCheckbox" type="checkbox" selector="table.data-grid tbody > tr:nth-of-type({{row}}) td.data-grid-checkbox-cell input" parameterized="true"/> <element name="row" type="text" selector="table.data-grid tbody > tr:nth-of-type({{row}})" parameterized="true"/> <element name="rows" type="text" selector="table.data-grid tbody > tr.data-row"/> <!--Specific cell e.g. {{Section.gridCell('1', 'Name')}}--> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminGridControlsSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminGridControlsSection.xml index ac1e461e9e16b..978a09db82b16 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminGridControlsSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminGridControlsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <!-- TODO: Search, Notifications, Admin Menu --> <section name="AdminGridMainControls"> <element name="add" type="button" selector="#add" timeout="30"/> @@ -27,7 +27,7 @@ <element name="filters" type="button" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] button[data-action='grid-filter-expand']" timeout="5"/> <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> <element name="cancel" type="button" selector="button[data-action='grid-filter-cancel']" timeout="30"/> - <element name="clearAll" type="button" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] button[data-action='grid-filter-reset']" timeout="5"/> + <element name="clearAll" type="button" selector="(//*[contains(@class, 'admin__data-grid-header')][contains(@data-bind, 'afterRender: \$data.setToolbarNode')]//button[contains(@data-action, 'reset')])[1]" timeout="5"/> </section> <section name="AdminGridDefaultViewControls"> <element name="defaultView" type="button" selector=".admin__data-grid-header[data-bind='afterRender: \$data.setToolbarNode'] .admin__data-grid-action-bookmarks" timeout="5"/> @@ -54,7 +54,8 @@ <!-- TODO: Pagination controls --> <section name="AdminGridHeaders"> <element name="title" type="text" selector=".page-title-wrapper h1"/> - <element name="headerByName" type="text" selector="//span[@class='data-grid-cell-content' and contains(text(), '{{var1}}')]/parent::*" parameterized="true"/> + <element name="headerByName" type="text" selector="//div[@data-role='grid-wrapper']//span[@class='data-grid-cell-content' and contains(text(), '{{var1}}')]/parent::*" parameterized="true"/> + <element name="columnsNames" type="text" selector="[data-role='grid-wrapper'] .data-grid-th > span"/> </section> <section name="AdminGridRow"> <element name="rowOne" type="text" selector="tr[data-repeat-index='0']"/> @@ -65,4 +66,16 @@ <element name="checkboxByValue" type="checkbox" selector="//input[ancestor::tr[contains(., '{{var1}}')]]" parameterized="true"/> <element name="checkboxByIndex" type="checkbox" selector=".data-row[data-repeat-index='{{var1}}'] .admin__control-checkbox" parameterized="true"/> </section> + <section name="AdminGridSelectRows"> + <element name="multicheckDropdown" type="button" selector="div[data-role='grid-wrapper'] th.data-grid-multicheck-cell button.action-multicheck-toggle"/> + <element name="multicheckOption" type="button" selector="//div[@data-role='grid-wrapper']//th[contains(@class, data-grid-multicheck-cell)]//li//span[text() = '{{label}}']" parameterized="true"/> + <element name="bulkActionDropdown" type="button" selector="div.admin__data-grid-header-row.row div.action-select-wrap button.action-select"/> + <element name="bulkActionOption" type="button" selector="//div[contains(@class,'admin__data-grid-header-row') and contains(@class, 'row')]//div[contains(@class, 'action-select-wrap')]//ul/li/span[text() = '{{label}}']" parameterized="true"/> + </section> + <section name="AdminGridConfirmActionSection"> + <element name="title" type="text" selector=".modal-popup.confirm h1.modal-title"/> + <element name="message" type="text" selector=".modal-popup.confirm div.modal-content"/> + <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> + </section> </sections> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml new file mode 100644 index 0000000000000..cb1fdd716b174 --- /dev/null +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminMessagesSection"> + <element name="successMessage" type="text" selector=".message-success"/> + <element name="errorMessage" type="text" selector=".message.message-error.error"/> + </section> +</sections> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml index 8c3dd505f66be..35ec242f05c52 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ModalConfirmationSection"> <element name="CancelButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-dismiss')]"/> <element name="OkButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-accept')]"/> diff --git a/app/code/Magento/Ui/Test/Mftf/composer.json b/app/code/Magento/Ui/Test/Mftf/composer.json deleted file mode 100644 index 86a99d02bd959..0000000000000 --- a/app/code/Magento/Ui/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-ui", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php index d814fdcd153da..32524e2331ba7 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Filters/Type/InputTest.php @@ -6,7 +6,6 @@ namespace Magento\Ui\Test\Unit\Component\Filters\Type; use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface as UiContext; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Ui\Component\Filters\Type\Input; @@ -37,18 +36,18 @@ class InputTest extends \PHPUnit\Framework\TestCase protected $filterModifierMock; /** - * Set up + * @inheritdoc */ protected function setUp() { $this->contextMock = $this->getMockForAbstractClass( - \Magento\Framework\View\Element\UiComponent\ContextInterface::class, + ContextInterface::class, [], '', false ); $this->uiComponentFactory = $this->createPartialMock( - \Magento\Framework\View\Element\UiComponentFactory::class, + UiComponentFactory::class, ['create'] ); $this->filterBuilderMock = $this->createMock(\Magento\Framework\Api\FilterBuilder::class); @@ -63,7 +62,7 @@ protected function setUp() * * @return void */ - public function testGetComponentName() + public function testGetComponentName(): void { $this->contextMock->expects($this->never())->method('getProcessor'); $date = new Input( @@ -86,7 +85,7 @@ public function testGetComponentName() * @dataProvider getPrepareDataProvider * @return void */ - public function testPrepare($name, $filterData, $expectedCondition) + public function testPrepare(string $name, array $filterData, ?array $expectedCondition): void { $processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) ->disableOriginalConstructor() @@ -94,7 +93,7 @@ public function testPrepare($name, $filterData, $expectedCondition) $this->contextMock->expects($this->atLeastOnce())->method('getProcessor')->willReturn($processor); /** @var UiComponentInterface $uiComponent */ $uiComponent = $this->getMockForAbstractClass( - \Magento\Framework\View\Element\UiComponentInterface::class, + UiComponentInterface::class, [], '', false @@ -169,7 +168,7 @@ public function testPrepare($name, $filterData, $expectedCondition) /** * @return array */ - public function getPrepareDataProvider() + public function getPrepareDataProvider(): array { return [ [ @@ -177,6 +176,16 @@ public function getPrepareDataProvider() ['test_date' => ''], null, ], + [ + 'test_date', + ['test_date' => null], + null, + ], + [ + 'test_date', + ['test_date' => '0'], + ['like' => '%0%'], + ], [ 'test_date', ['test_date' => 'some_value'], diff --git a/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php b/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php index debcde4765fc7..cdb62ce1db68d 100644 --- a/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php +++ b/app/code/Magento/Ui/Test/Unit/DataProvider/EavValidationRulesTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Ui\Test\Unit\DataProvider; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; @@ -29,6 +30,9 @@ class EavValidationRulesTest extends \PHPUnit\Framework\TestCase */ protected $attributeMock; + /** + * {@inheritDoc} + */ protected function setUp() { $this->objectManager = new ObjectManager($this); @@ -42,11 +46,13 @@ protected function setUp() } /** + * @param string $attributeInputType + * @param mixed $validateRules * @param array $data * @param array $expected * @dataProvider buildDataProvider */ - public function testBuild($attributeInputType, $validateRules, $data, $expected) + public function testBuild($attributeInputType, $validateRules, $data, $expected): void { $this->attributeMock->expects($this->once())->method('getFrontendInput')->willReturn($attributeInputType); $this->attributeMock->expects($this->any())->method('getValidateRules')->willReturn($validateRules); @@ -70,11 +76,25 @@ public function buildDataProvider() ['', ['input_validation' => 'email'], [], ['validate-email' => true]], ['', ['input_validation' => 'date'], [], ['validate-date' => true]], ['', ['input_validation' => 'other'], [], []], - ['', ['max_text_length' => '254'], ['required' => 1], ['max_text_length' => 254, 'required-entry' => true]], - ['', ['max_text_length' => '254', 'min_text_length' => 1], [], - ['max_text_length' => 254, 'min_text_length' => 1]], - ['', ['max_text_length' => '254', 'input_validation' => 'date'], [], - ['max_text_length' => 254, 'validate-date' => true]], + ['', ['max_text_length' => '254'], ['required' => 1], ['required-entry' => true]], + [ + '', + ['input_validation' => 'other', 'max_text_length' => '254'], + ['required' => 1], + ['max_text_length' => 254, 'required-entry' => true] + ], + [ + '', + ['input_validation' => 'other', 'max_text_length' => '254', 'min_text_length' => 1], + [], + ['max_text_length' => 254, 'min_text_length' => 1] + ], + [ + '', + ['max_text_length' => '254', 'input_validation' => 'date'], + [], + ['max_text_length' => 254, 'validate-date' => true] + ], ]; } } diff --git a/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php b/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php index dd6fd58d355e6..aae4fafbdac3f 100644 --- a/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php +++ b/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php @@ -40,18 +40,37 @@ protected function setUp() ); } - public function testGetHeaders() + /** + * @param array $columnLabels + * @param array $expected + * @return void + * @dataProvider getColumnsDataProvider + */ + public function testGetHeaders(array $columnLabels, array $expected): void { $componentName = 'component_name'; $columnName = 'column_name'; - $columnLabel = 'column_label'; - - $component = $this->prepareColumns($componentName, $columnName, $columnLabel); + $component = $this->prepareColumns($componentName, $columnName, $columnLabels[0]); $result = $this->model->getHeaders($component); $this->assertTrue(is_array($result)); $this->assertCount(1, $result); - $this->assertEquals($columnLabel, $result[0]); + $this->assertEquals($expected, $result); + } + + /** + * @return array + */ + public function getColumnsDataProvider(): array + { + return [ + [['ID'],['"ID"']], + [['Name'],['Name']], + [['Id'],['Id']], + [['id'],['id']], + [['IDTEST'],['"IDTEST"']], + [['ID TEST'],['"ID TEST"']], + ]; } public function testGetFields() diff --git a/app/code/Magento/Ui/etc/adminhtml/system.xml b/app/code/Magento/Ui/etc/adminhtml/system.xml index 74e2e076b93c8..ab4272f8d2a34 100644 --- a/app/code/Magento/Ui/etc/adminhtml/system.xml +++ b/app/code/Magento/Ui/etc/adminhtml/system.xml @@ -9,12 +9,12 @@ <system> <section id="dev"> <group id="js"> - <field id="session_storage_logging" translate="label" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="session_storage_logging" translate="label comment" type="select" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Log JS Errors to Session Storage</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>If enabled, can be used by functional tests for extended reporting</comment> </field> - <field id="session_storage_key" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="session_storage_key" translate="label comment" type="text" sortOrder="110" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Log JS Errors to Session Storage Key</label> <comment>Use this key to retrieve collected js errors</comment> </field> diff --git a/app/code/Magento/Ui/etc/db_schema_whitelist.json b/app/code/Magento/Ui/etc/db_schema_whitelist.json index 662d6234ab96d..16d441f6d3ada 100644 --- a/app/code/Magento/Ui/etc/db_schema_whitelist.json +++ b/app/code/Magento/Ui/etc/db_schema_whitelist.json @@ -1,22 +1,22 @@ { - "ui_bookmark": { - "column": { - "bookmark_id": true, - "user_id": true, - "namespace": true, - "identifier": true, - "current": true, - "title": true, - "config": true, - "created_at": true, - "updated_at": true - }, - "index": { - "UI_BOOKMARK_USER_ID_NAMESPACE_IDENTIFIER": true - }, - "constraint": { - "PRIMARY": true, - "UI_BOOKMARK_USER_ID_ADMIN_USER_USER_ID": true + "ui_bookmark": { + "column": { + "bookmark_id": true, + "user_id": true, + "namespace": true, + "identifier": true, + "current": true, + "title": true, + "config": true, + "created_at": true, + "updated_at": true + }, + "index": { + "UI_BOOKMARK_USER_ID_NAMESPACE_IDENTIFIER": true + }, + "constraint": { + "PRIMARY": true, + "UI_BOOKMARK_USER_ID_ADMIN_USER_USER_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv index ed4386f6000c3..106526f66e708 100644 --- a/app/code/Magento/Ui/i18n/en_US.csv +++ b/app/code/Magento/Ui/i18n/en_US.csv @@ -125,30 +125,18 @@ Ok,Ok "Label Actions Two","Label Actions Two" "Some Dynamic Actions","Some Dynamic Actions" "Log JS Errors to Session Storage","Log JS Errors to Session Storage" +"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" "Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" +"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" settings/label,settings/label settings/confirm/title,settings/confirm/title -"settings/confirm/message - ","settings/confirm/message - " -" - settings/callback/provider - "," - settings/callback/provider - " -"settings/callback/target - ","settings/callback/target - " -"settings/newViewLabel - ","settings/newViewLabel - " +"settings/confirm/message","settings/confirm/message" +"settings/callback/provider","settings/callback/provider" +"settings/callback/target","settings/callback/target" +"settings/newViewLabel","settings/newViewLabel" settings/title,settings/title settings/description,settings/description -" - settings/addButtonLabel - "," - settings/addButtonLabel - " +"settings/addButtonLabel","settings/addButtonLabel" " formElements/*[name(.)=../../@formElement]/settings/description "," diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml b/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml index 94272f723ec77..ccd702c23ea65 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml @@ -16,15 +16,11 @@ <item name="url" type="url" xsi:type="converter">settings/url</item> <item name="confirm" xsi:type="array"> <item name="title" type="string" translate="true" xsi:type="xpath">settings/confirm/title</item> - <item name="message" type="string" translate="true" xsi:type="xpath">settings/confirm/message - </item> + <item name="message" type="string" translate="true" xsi:type="xpath">settings/confirm/message</item> </item> <item name="callback" xsi:type="array"> - <item name="provider" type="string" translate="true" xsi:type="xpath"> - settings/callback/provider - </item> - <item name="target" type="string" translate="true" xsi:type="xpath">settings/callback/target - </item> + <item name="provider" type="string" translate="true" xsi:type="xpath">settings/callback/provider</item> + <item name="target" type="string" translate="true" xsi:type="xpath">settings/callback/target</item> </item> </item> </argument> @@ -59,8 +55,7 @@ <item name="config" xsi:type="array"> <item name="childDefaults" type="item" xsi:type="converter">settings/childDefaults</item> <item name="viewTmpl" type="string" xsi:type="xpath">settings/viewTmpl</item> - <item name="newViewLabel" translate="true" type="string" xsi:type="xpath">settings/newViewLabel - </item> + <item name="newViewLabel" translate="true" type="string" xsi:type="xpath">settings/newViewLabel</item> </item> </argument> </schema> @@ -265,9 +260,7 @@ <item name="columnsHeaderClasses" type="string" xsi:type="xpath">settings/columnsHeaderClasses</item> <item name="recordTemplate" type="string" xsi:type="xpath">settings/recordTemplate</item> <item name="collapsibleHeader" type="boolean" xsi:type="xpath">settings/collapsibleHeader</item> - <item name="addButtonLabel" type="string" translate="true" xsi:type="xpath"> - settings/addButtonLabel - </item> + <item name="addButtonLabel" type="string" translate="true" xsi:type="xpath">settings/addButtonLabel</item> <item name="addButton" type="boolean" xsi:type="xpath">settings/addButton</item> <item name="deleteProperty" type="string" xsi:type="xpath">settings/deleteProperty</item> <item name="identificationProperty" type="string" xsi:type="xpath">settings/identificationProperty</item> diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 7537107560cb4..1d52fc78d7a85 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -330,9 +330,7 @@ define([ } if (this.defaultPagesState[this.currentPage()]) { - this.pagesChanged[this.currentPage()] = - !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); - this.changed(_.some(this.pagesChanged)); + this.setChangedForCurrentPage(); } }, @@ -442,13 +440,9 @@ define([ return initialize; })); - this.pagesChanged[this.currentPage()] = - !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); - this.changed(_.some(this.pagesChanged)); + this.setChangedForCurrentPage(); } else if (this.hasInitialPagesState[this.currentPage()]) { - this.pagesChanged[this.currentPage()] = - !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); - this.changed(_.some(this.pagesChanged)); + this.setChangedForCurrentPage(); } }, @@ -848,7 +842,8 @@ define([ deleteRecord: function (index, recordId) { var recordInstance, lastRecord, - recordsData; + recordsData, + lastRecordIndex; if (this.deleteProperty) { recordsData = this.recordData(); @@ -867,12 +862,13 @@ define([ this.update = true; if (~~this.currentPage() === this.pages()) { + lastRecordIndex = this.startIndex + this.getChildItems().length - 1; lastRecord = _.findWhere(this.elems(), { - index: this.startIndex + this.getChildItems().length - 1 + index: lastRecordIndex }) || _.findWhere(this.elems(), { - index: (this.startIndex + this.getChildItems().length - 1).toString() + index: lastRecordIndex.toString() }); lastRecord.destroy(); @@ -1133,6 +1129,18 @@ define([ }); this.isDifferedFromDefault(!_.isEqual(recordData, this.default)); + }, + + /** + * Set the changed property if the current page is different + * than the default state + * + * @return void + */ + setChangedForCurrentPage: function () { + this.pagesChanged[this.currentPage()] = + !compareArrays(this.defaultPagesState[this.currentPage()], this.arrayFilter(this.getChildItems())); + this.changed(_.some(this.pagesChanged)); } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js index b729dd3127d90..6d33386fa1f1c 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js @@ -22,7 +22,7 @@ define([ opened: false, level: 0, visible: true, - initializeFieldsetDataByDefault: false, /* Data in some fieldsets should be initialized before open */ + initializeFieldsetDataByDefault: false, /* Data in some fieldsets should be initialized before open */ disabled: false, listens: { 'opened': 'onVisibilityChange' @@ -77,9 +77,9 @@ define([ elem.initContainer(this); elem.on({ - 'update': this.onChildrenUpdate, - 'loading': this.onContentLoading, - 'error': this.onChildrenError + 'update': this.onChildrenUpdate, + 'loading': this.onContentLoading, + 'error': this.onChildrenError }); if (this.disabled) { @@ -155,11 +155,42 @@ define([ * @param {String} message - error message. */ onChildrenError: function (message) { - var hasErrors = this.elems.some('error'); + var hasErrors = false; + + if (!message) { + hasErrors = this._isChildrenHasErrors(hasErrors, this); + } this.error(hasErrors || message); }, + /** + * Returns errors of children if exist + * + * @param {Boolean} hasErrors + * @param {*} container + * @return {Boolean} + * @private + */ + _isChildrenHasErrors: function (hasErrors, container) { + var self = this; + + if (hasErrors === false && container.hasOwnProperty('elems')) { + hasErrors = container.elems.some('error'); + + if (hasErrors === false && container.hasOwnProperty('_elems')) { + container._elems.forEach(function (child) { + + if (hasErrors === false) { + hasErrors = self._isChildrenHasErrors(hasErrors, child); + } + }); + } + } + + return hasErrors; + }, + /** * Callback that sets loading property to true. */ diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js index 5177b4a378d69..8f1a75d2be0d4 100755 --- a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js @@ -40,6 +40,7 @@ define([ showFallbackReset: false, additionalClasses: {}, isUseDefault: '', + serviceDisabled: false, valueUpdate: false, // ko binding valueUpdate switcherConfig: { @@ -97,7 +98,7 @@ define([ this._super(); this.observe('error disabled focused preview visible value warn notice isDifferedFromDefault') - .observe('isUseDefault') + .observe('isUseDefault serviceDisabled') .observe({ 'required': !!rules['required-entry'] }); @@ -407,7 +408,7 @@ define([ this.bubble('error', message); //TODO: Implement proper result propagation for form - if (!isValid) { + if (this.source && !isValid) { this.source.set('params.invalid', true); } @@ -449,6 +450,10 @@ define([ */ toggleUseDefault: function (state) { this.disabled(state); + + if (this.source && this.hasService()) { + this.source.set('data.use_default.' + this.index, Number(state)); + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index b583d0be69f34..6492d0821fc21 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -6,6 +6,7 @@ /** * @api */ +/* global Base64 */ define([ 'jquery', 'underscore', @@ -168,6 +169,10 @@ define([ processFile: function (file) { file.previewType = this.getFilePreviewType(file); + if (!file.id && file.name) { + file.id = Base64.mageEncode(file.name); + } + this.observe.call(file, true, [ 'previewWidth', 'previewHeight' diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js index 69c9fb74cbce1..0ae09f14fa946 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js @@ -17,6 +17,15 @@ define([ 'use strict'; return Element.extend({ + /** + * {@inheritDoc} + */ + initialize: function () { + this._super(); + + // Listen for file deletions from the media browser + $(window).on('fileDeleted.mediabrowser', this.onDeleteFile.bind(this)); + }, /** * Assign uid for media gallery @@ -76,6 +85,40 @@ define([ browser.openDialog(openDialogUrl, null, null, this.mediaGallery.openDialogTitle); }, + /** + * @param {jQuery.event} e + * @param {Object} data + * @returns {Object} Chainables + */ + onDeleteFile: function (e, data) { + var fileId = this.getFileId(), + deletedFileIds = data.ids; + + if (fileId && $.inArray(fileId, deletedFileIds) > -1) { + this.clear(); + } + + return this; + }, + + /** + * {@inheritDoc} + */ + clear: function () { + this.value([]); + + return this; + }, + + /** + * Gets the ID of the file used if set + * + * @return {String|Null} ID + */ + getFileId: function () { + return this.hasData() ? this.value()[0].id : null; + }, + /** * Trigger native browser file upload UI via clicking on 'Upload' button * diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/region.js b/app/code/Magento/Ui/view/base/web/js/form/element/region.js index 0edb4c1966b54..45d38b339b50b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/region.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/region.js @@ -49,6 +49,10 @@ define([ if (option && !option['is_region_required']) { this.error(false); this.validation = _.omit(this.validation, 'required-entry'); + registry.get(this.customName, function (input) { + input.validation['required-entry'] = false; + input.required(false); + }); } else { this.validation['required-entry'] = true; } @@ -57,6 +61,7 @@ define([ registry.get(this.customName, function (input) { isRegionRequired = !!option['is_region_required']; input.validation['required-entry'] = isRegionRequired; + input.validation['validate-not-number-first'] = true; input.required(isRegionRequired); }); } diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/single-checkbox-use-config.js b/app/code/Magento/Ui/view/base/web/js/form/element/single-checkbox-use-config.js index b20b2c31ee1fb..3ee7ddd1e738f 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/single-checkbox-use-config.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/single-checkbox-use-config.js @@ -36,6 +36,10 @@ define([ */ toggleElement: function () { this.disabled(this.isUseDefault() || this.isUseConfig()); + + if (this.source) { + this.source.set('data.use_default.' + this.index, Number(this.isUseDefault())); + } } }); }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js index a4c56958911a8..be7a1a13fbd61 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js @@ -78,8 +78,8 @@ define([ }, /** - * Adds new action. If action with a specfied identifier - * already exists, than the original will be overrided. + * Adds new action. If an action with the specified identifier + * already exists, then the original will be overridden. * * @param {String} index - Actions' identifier. * @param {Object} action - Actions' data. @@ -108,7 +108,7 @@ define([ /** * Processes actions, setting additional information to them and - * evaluating ther properties as a string templates. + * evaluating their properties as string templates. * * @private * @param {Object} row - Row object. @@ -204,11 +204,11 @@ define([ }, /** - * Creates action callback based on its' data. If action doesn't spicify + * Creates action callback based on it's data. If the action doesn't specify * a callback function than the default one will be used. * * @private - * @param {Object} action - Actions' object. + * @param {Object} action - Action's object. * @returns {Function} Callback function. */ _getCallback: function (action) { @@ -234,7 +234,7 @@ define([ * Creates action callback for multiple actions. * * @private - * @param {Object} action - Actions' object. + * @param {Object} action - Action's object. * @returns {Function} Callback function. */ _getCallbacks: function (action) { @@ -259,12 +259,12 @@ define([ /** * Default action callback. Redirects to - * the specified in actions' data url. + * the specified in action's data url. * - * @param {String} actionIndex - Actions' identifier. - * @param {(Number|String)} recordId - Id of the record accociated - * with a specfied action. - * @param {Object} action - Actions' data. + * @param {String} actionIndex - Action's identifier. + * @param {(Number|String)} recordId - Id of the record associated + * with a specified action. + * @param {Object} action - Action's data. */ defaultCallback: function (actionIndex, recordId, action) { window.location.href = action.href; @@ -273,7 +273,7 @@ define([ /** * Shows actions' confirmation window. * - * @param {Object} action - Actions' data. + * @param {Object} action - Action's data. * @param {Function} callback - Callback that will be * invoked if action is confirmed. */ diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js index 0a29728390592..ba0f4d25c25a4 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/multiselect.js @@ -229,6 +229,15 @@ define([ return this; }, + /** + * Selects or deselects all records on the current page. + * + * @returns {Multiselect} Chainable. + */ + togglePage: function () { + return this.isPageSelected() ? this.deselectPage() : this.selectPage(); + }, + /** * Clears the array of not selected records. * @@ -259,7 +268,7 @@ define([ * Returns identifier of a record. * * @param {*} id - Id of a record or its' index in a rows array. - * @param {Boolean} [isIndex=false] - Flag that specifies whith what + * @param {Boolean} [isIndex=false] - Flag that specifies with what * kind of identifier we are dealing with. * @returns {*} */ diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js index ecc4ec1902d87..98c3eb1c6f882 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/filters.js @@ -257,7 +257,7 @@ define([ /** * Creates filter component configuration associated with the provided column. * - * @param {Column} column - Column component whith a basic filter declaration. + * @param {Column} column - Column component with a basic filter declaration. * @returns {Object} Filters' configuration. */ buildFilter: function (column) { @@ -331,7 +331,7 @@ define([ }, /** - * Finds filters whith a not empty data + * Finds filters with a not empty data * and sets them to the 'active' filters array. * * @returns {Filters} Chainable. diff --git a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js index 046ff12bfaa6f..48c04458ff49a 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/massactions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/massactions.js @@ -175,11 +175,14 @@ define([ * invoked if action is confirmed. */ _confirm: function (action, callback) { - var confirmData = action.confirm; + var confirmData = action.confirm, + data = this.getSelections(), + total = data.total ? data.total : 0, + confirmMessage = confirmData.message + ' (' + total + ' record' + (total > 1 ? 's' : '') + ')'; confirm({ title: confirmData.title, - content: confirmData.message, + content: confirmMessage, actions: { confirm: callback } diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/element/element.js b/app/code/Magento/Ui/view/base/web/js/lib/core/element/element.js index b228494b422c3..b05fed939b7a5 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/element/element.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/element/element.js @@ -46,7 +46,7 @@ define([ } /** - * Creates observable propery using 'track' method. + * Creates observable property using 'track' method. * * @param {Object} obj - Object to whom property belongs. * @param {String} key - Key of the property. @@ -66,7 +66,7 @@ define([ Element = _.extend({ defaults: { - _requesetd: {}, + _requested: {}, containers: [], exports: {}, imports: {}, @@ -249,7 +249,7 @@ define([ * @returns {Function} Async module wrapper. */ requestModule: function (name) { - var requested = this._requesetd; + var requested = this._requested; if (!requested[name]) { requested[name] = registry.async(name); diff --git a/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js b/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js index 5c3c71e31823d..826e8ec8c33b4 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js @@ -17,7 +17,7 @@ define([ var privateData = new WeakMap(); /** - * Extarcts private items storage associated + * Extracts private item storage associated * with a provided registry instance. * * @param {Object} container @@ -39,7 +39,7 @@ define([ } /** - * Wrapper function used for convinient access to the elements. + * Wrapper function used for convenient access to the elements. * See 'async' method for examples of usage and comparison * with a regular 'get' method. * @@ -139,7 +139,7 @@ define([ * which matches specified search criteria. * * @param {Object} data - Data object where to perform a lookup. - * @param {(String|Object|Function)} query - Seach criteria. + * @param {(String|Object|Function)} query - Search criteria. * @param {Boolean} findAll - Flag that defines whether to * search for all applicable items or to stop on a first found entry. * @returns {Array|Object|*} @@ -322,8 +322,8 @@ define([ /** * Creates a wrapper function over the provided search query - * in order to provide somehow more convinient access to the - * registrie's items. + * in order to provide somehow more convenient access to the + * registry's items. * * @param {(String|Object|Function)} query - Search criteria. * See 'get' method for the syntax examples. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 41e2703f4ed1e..1103f3e54a4cf 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -221,7 +221,7 @@ define([ ], 'time12h': [ function (value) { - return /^((0?[1-9]|1[012])(:[0-5]\d){0,2}(\ [AP]M))$/i.test(value); + return /^((0?[1-9]|1[012])(:[0-5]\d){0,2}(\s[AP]M))$/i.test(value); }, $.mage.__('Please enter a valid time, between 00:00 am and 12:00 pm') ], @@ -636,7 +636,7 @@ define([ 'validate-number': [ function (value) { return utils.isEmptyNoTrim(value) || - !isNaN(utils.parseNumber(value)) && /^\s*-?\d*(\.\d*)?\s*$/.test(value); + !isNaN(utils.parseNumber(value)) && /^\s*-?\d*(,\d*)*(\.\d*)?\s*$/.test(value); }, $.mage.__('Please enter a valid number in this field.') ], @@ -755,6 +755,12 @@ define([ }, $.mage.__('Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.')//eslint-disable-line max-len ], + 'validate-not-number-first': [ + function (value) { + return utils.isEmptyNoTrim(value) || /^[^0-9-\.].*$/.test(value.trim()); + }, + $.mage.__('First character must be letter.') + ], 'validate-date': [ function (value, params, additionalParams) { var test = moment(value, additionalParams.dateFormat); diff --git a/app/code/Magento/Ui/view/base/web/js/modal/confirm.js b/app/code/Magento/Ui/view/base/web/js/modal/confirm.js index 9cb14f7b2ef11..eceab940d1141 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/confirm.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/confirm.js @@ -9,10 +9,10 @@ define([ 'jquery', 'underscore', + 'mage/translate', 'jquery/ui', - 'Magento_Ui/js/modal/modal', - 'mage/translate' -], function ($, _) { + 'Magento_Ui/js/modal/modal' +], function ($, _, $t) { 'use strict'; $.widget('mage.confirm', $.mage.modal, { @@ -38,7 +38,7 @@ define([ cancel: function () {} }, buttons: [{ - text: $.mage.__('Cancel'), + text: $t('Cancel'), class: 'action-secondary action-dismiss', /** @@ -48,7 +48,7 @@ define([ this.closeModal(event); } }, { - text: $.mage.__('OK'), + text: $t('OK'), class: 'action-primary action-accept', /** diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js index 147a18c08c54c..f016b0dbefd87 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js @@ -340,6 +340,12 @@ define([ var zIndex = this.modal.zIndex(), baseIndex = zIndex + this._getVisibleCount(); + if (this.modal.data('active')) { + return; + } + + this.modal.data('active', true); + this.overlay.zIndex(++baseIndex); this.prevOverlayIndex = this.overlay.zIndex(); this.modal.zIndex(this.overlay.zIndex() + 1); @@ -354,6 +360,7 @@ define([ */ _unsetActive: function () { this.modal.removeAttr('style'); + this.modal.data('active', false); if (this.overlay) { // In cases when one modal is closed but there is another modal open (e.g. admin notifications) diff --git a/app/code/Magento/Ui/view/base/web/templates/block-loader.html b/app/code/Magento/Ui/view/base/web/templates/block-loader.html index 3d8b730a9e8c5..cb28b09e4da83 100644 --- a/app/code/Magento/Ui/view/base/web/templates/block-loader.html +++ b/app/code/Magento/Ui/view/base/web/templates/block-loader.html @@ -6,6 +6,6 @@ --> <div data-role="loader" class="loading-mask" style="position: absolute;"> <div class="loader"> - <img src="<%= loaderImageHref %>" alt="Loading..." style="position: absolute;"> + <img src="<%= loaderImageHref %>" alt="Loading..." title="Loading..." style="position: absolute;"> </div> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/components/collection.html b/app/code/Magento/Ui/view/base/web/templates/form/components/collection.html index e153beab8748b..873b0ca744a88 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/components/collection.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/components/collection.html @@ -30,7 +30,7 @@ <legend class="admin__legend"> <span text="$parent.label"/> </legend><br /> - + <each args="getRegion('body')" render=""/> </fieldset> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/components/collection/preview.html b/app/code/Magento/Ui/view/base/web/templates/form/components/collection/preview.html index 2c0ce802b4bc0..806ac767d7b59 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/components/collection/preview.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/components/collection/preview.html @@ -11,7 +11,7 @@ 'company', 'street', { - items: 'city region_id postcode', + items: 'city region_id region_id_input postcode', separator: ', ' }, 'country_id', diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/checkbox-set.html b/app/code/Magento/Ui/view/base/web/templates/form/element/checkbox-set.html index 79478d2417110..ce06201adb8ba 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/element/checkbox-set.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/checkbox-set.html @@ -16,14 +16,14 @@ <div class="admin__field-control" css="'_with-tooltip': $data.tooltip"> <div class="admin__field admin__field-option" outereach="options"> - <input + <input ko-checked="$parent.value" ko-disabled="$parent.disabled" css=" 'admin__control-radio': !$parent.multiple, 'admin__control-checkbox': $parent.multiple" attr=" - id: ++ko.uid, + id: ++ko.uid, value: value, type: $parent.multiple ? 'checkbox' : 'radio'"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/helper/service.html b/app/code/Magento/Ui/view/base/web/templates/form/element/helper/service.html index e3c3ab5b5df5c..195104b36721e 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/element/helper/service.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/element/helper/service.html @@ -10,8 +10,8 @@ attr=" id: $data.uid + '_default', name: 'use_default[' + $data.index + ']', - 'data-form-part': $data.ns " - ko-checked="isUseDefault"> + ko-checked="isUseDefault" + ko-disabled="$data.serviceDisabled"> <label translate="'Use Default Value'" attr="for: $data.uid + '_default'" class="admin__field-label"></label> </div> diff --git a/app/code/Magento/Ui/view/base/web/templates/form/field.html b/app/code/Magento/Ui/view/base/web/templates/form/field.html index 6143900d5dd7b..ed84e158819a2 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/field.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/field.html @@ -8,9 +8,11 @@ visible="visible" css="$data.additionalClasses" attr="'data-index': index"> - <label class="admin__field-label" if="$data.label" visible="$data.labelVisible" attr="for: uid"> - <span translate="label" attr="'data-config-scope': $data.scopeLabel"/> - </label> + <div class="admin__field-label"> + <label if="$data.label" visible="$data.labelVisible" attr="for: uid"> + <span translate="label" attr="'data-config-scope': $data.scopeLabel" /> + </label> + </div> <div class="admin__field-control" css="'_with-tooltip': $data.tooltip, '_with-reset': $data.showFallbackReset && $data.isDifferedFromDefault"> <render args="elementTmpl" ifnot="hasAddons()"/> @@ -35,7 +37,7 @@ <div class="admin__field-note" if="$data.notice" attr="id: noticeId"> <span translate="notice"/> </div> - + <div class="admin__additional-info" if="$data.additionalInfo" html="$data.additionalInfo"></div> <render args="$data.service.template" if="$data.hasService()"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html b/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html index 18dd49bd56ee3..e6e14d5aa335b 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/columns/multiselect.html @@ -11,7 +11,7 @@ data-bind=" checked: allSelected(), attr: {id: ++ko.uid}, - event: { change: toggleSelectAll }, + event: { change: togglePage }, css: { '_indeterminate': indetermine }, enable: totalRecords"> <label attr="for: ko.uid"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html b/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html index a11e56a68dab2..81c7acaf4aa33 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/editing/bulk.html @@ -13,7 +13,7 @@ </button> </with> </if> - + <if args="$data.isEditor"> <label class="admin__field-label admin__field-label-vertical" attr="for: uid" translate="'All in Column'"/> <render/> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/editing/row.html b/app/code/Magento/Ui/view/base/web/templates/grid/editing/row.html index 4b3109e522f0b..c82456b2a357c 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/editing/row.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/editing/row.html @@ -11,8 +11,8 @@ <span class="data-grid-row-changed" css="_changed: $parent.hasChanges"> <span class="data-grid-row-changed-tooltip" translate="'Record contains unsaved changes.'"/> </span> - </td> - + </td> + <!-- ko ifnot: $parent.isActionsColumn($data) --> <td if="$col.isEditor" template="$parent.fieldTmpl"/> <td ifnot="$col.isEditor" css="$col.getFieldClass()" template="$col.getBody()"/> diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html index 0bd5ea06dbf86..8d79bfe6a1b4c 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/sticky/sticky.html @@ -7,7 +7,7 @@ <div style="display: none;" css="stickyClass" afterRender="setStickyNode"> <span class="data-grid-cap-left" afterRender="setLeftCap"/> <span class="data-grid-cap-right" afterRender="setRightCap"/> - + <div afterRender="setStickyToolbarNode"> <div class="admin__data-grid-header"> <div class="admin__data-grid-header-row"> diff --git a/app/code/Magento/Ui/view/base/web/templates/group/group.html b/app/code/Magento/Ui/view/base/web/templates/group/group.html index 13abdb15d965c..e30ac7a377542 100644 --- a/app/code/Magento/Ui/view/base/web/templates/group/group.html +++ b/app/code/Magento/Ui/view/base/web/templates/group/group.html @@ -19,7 +19,7 @@ <render args="elementTmpl" if="element.input_type == 'checkbox' || element.input_type == 'radio'"/> </if> </each> - + <each args="getRegion('insideGroup')" render=""/> <each args="elems" if="validateWholeGroup"> diff --git a/app/code/Magento/Ui/view/frontend/web/js/model/messages.js b/app/code/Magento/Ui/view/frontend/web/js/model/messages.js index b970b2236155f..fb9a20c054da2 100644 --- a/app/code/Magento/Ui/view/frontend/web/js/model/messages.js +++ b/app/code/Magento/Ui/view/frontend/web/js/model/messages.js @@ -55,7 +55,7 @@ define([ return messageObj.parameters.shift(); }); this.clear(); - this.errorMessages.push(message); + type.push(message); return true; }, diff --git a/app/code/Magento/Ups/Test/Mftf/composer.json b/app/code/Magento/Ups/Test/Mftf/composer.json deleted file mode 100644 index 560eaab1a19b1..0000000000000 --- a/app/code/Magento/Ups/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-ups", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/UrlRewrite/Block/Catalog/Category/Tree.php b/app/code/Magento/UrlRewrite/Block/Catalog/Category/Tree.php index 64a775b01593b..e34d4773c271b 100644 --- a/app/code/Magento/UrlRewrite/Block/Catalog/Category/Tree.php +++ b/app/code/Magento/UrlRewrite/Block/Catalog/Category/Tree.php @@ -27,7 +27,7 @@ class Tree extends \Magento\Catalog\Block\Adminhtml\Category\AbstractCategory /** * @var string */ - protected $_template = 'categories.phtml'; + protected $_template = 'Magento_UrlRewrite::categories.phtml'; /** * Adminhtml data diff --git a/app/code/Magento/UrlRewrite/Block/Edit.php b/app/code/Magento/UrlRewrite/Block/Edit.php index baee8af893083..115c5db43a70a 100644 --- a/app/code/Magento/UrlRewrite/Block/Edit.php +++ b/app/code/Magento/UrlRewrite/Block/Edit.php @@ -65,7 +65,7 @@ public function __construct( */ protected function _prepareLayout() { - $this->setTemplate('edit.phtml'); + $this->setTemplate('Magento_UrlRewrite::edit.phtml'); $this->_addBackButton(); $this->_prepareLayoutFeatures(); @@ -243,7 +243,7 @@ private function _getSelectorBlock() * Since buttons are set as children, we remove them as children after generating them * not to duplicate them in future * - * @param null $area + * @param string $area * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ diff --git a/app/code/Magento/UrlRewrite/Block/Plugin/Store/Switcher/SetRedirectUrl.php b/app/code/Magento/UrlRewrite/Block/Plugin/Store/Switcher/SetRedirectUrl.php deleted file mode 100644 index 9bdff38e7aaa2..0000000000000 --- a/app/code/Magento/UrlRewrite/Block/Plugin/Store/Switcher/SetRedirectUrl.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\UrlRewrite\Block\Plugin\Store\Switcher; - -use Magento\Framework\Url\Helper\Data as UrlHelper; -use Magento\Framework\UrlInterface; -use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; -use Magento\Framework\App\RequestInterface; -use Magento\Framework\App\ActionInterface; - -class SetRedirectUrl -{ - /** - * @var \Magento\Framework\Url\Helper\Data - */ - private $urlHelper; - - /** - * @var \Magento\Framework\UrlInterface - */ - private $urlBuilder; - - /** - * @var \Magento\UrlRewrite\Model\UrlFinderInterface - */ - private $urlFinder; - - /** - * @var \Magento\Framework\App\RequestInterface - */ - private $request; - - /** - * @param UrlHelper $urlHelper - * @param UrlInterface $urlBuilder - * @param UrlFinderInterface $urlFinder - * @param RequestInterface $request - */ - public function __construct( - UrlHelper $urlHelper, - UrlInterface $urlBuilder, - UrlFinderInterface $urlFinder, - RequestInterface $request - ) { - $this->urlHelper = $urlHelper; - $this->urlBuilder = $urlBuilder; - $this->urlFinder = $urlFinder; - $this->request = $request; - } - - /** - * Set redirect url for store view based on request path info - * - * @param \Magento\Store\Block\Switcher $switcher - * @param \Magento\Store\Model\Store $store - * @param array $data - * @return array - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeGetTargetStorePostData( - \Magento\Store\Block\Switcher $switcher, - \Magento\Store\Model\Store $store, - $data = [] - ) { - $urlRewrite = $this->urlFinder->findOneByData([ - UrlRewrite::TARGET_PATH => $this->trimSlashInPath($this->request->getPathInfo()), - UrlRewrite::STORE_ID => $store->getId(), - ]); - if ($urlRewrite) { - $data[ActionInterface::PARAM_NAME_URL_ENCODED] = $this->urlHelper->getEncodedUrl( - $this->trimSlashInPath($this->urlBuilder->getUrl($urlRewrite->getRequestPath(), ['_scope' => $store])) - ); - } - return [$store, $data]; - } - - /** - * @param string $path - * @return string - */ - private function trimSlashInPath($path) - { - return trim($path, '/'); - } -} diff --git a/app/code/Magento/UrlRewrite/Block/Selector.php b/app/code/Magento/UrlRewrite/Block/Selector.php index 0a28ba215de5a..75266fd2f977c 100644 --- a/app/code/Magento/UrlRewrite/Block/Selector.php +++ b/app/code/Magento/UrlRewrite/Block/Selector.php @@ -18,7 +18,7 @@ class Selector extends \Magento\Backend\Block\Template /** * @var string */ - protected $_template = 'selector.phtml'; + protected $_template = 'Magento_UrlRewrite::selector.phtml'; /** * Set block template and get available modes diff --git a/app/code/Magento/UrlRewrite/Controller/Router.php b/app/code/Magento/UrlRewrite/Controller/Router.php index 73002c10cf1b6..ce432f24365a6 100644 --- a/app/code/Magento/UrlRewrite/Controller/Router.php +++ b/app/code/Magento/UrlRewrite/Controller/Router.php @@ -7,7 +7,6 @@ use Magento\Framework\App\RequestInterface; use Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; -use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\Framework\App\Request\Http as HttpRequest; @@ -78,44 +77,6 @@ public function __construct( */ public function match(RequestInterface $request) { - if ($fromStore = $request->getParam('___from_store')) { - //If we're in the process of switching stores then matching rewrite - //rule from previous store because the URL was not changed yet from - //old store's format. - $oldStoreId = $this->storeManager->getStore($fromStore)->getId(); - $oldRewrite = $this->getRewrite( - $request->getPathInfo(), - $oldStoreId - ); - if ($oldRewrite && $oldRewrite->getRedirectType() === 0) { - //If there is a match and it's a correct URL then just - //redirecting to current store's URL equivalent, - //otherwise just continuing finding a rule within current store. - $currentRewrite = $this->urlFinder->findOneByData( - [ - UrlRewrite::ENTITY_TYPE => $oldRewrite->getEntityType(), - UrlRewrite::ENTITY_ID => $oldRewrite->getEntityId(), - UrlRewrite::STORE_ID => - $this->storeManager->getStore()->getId(), - UrlRewrite::REDIRECT_TYPE => 0, - ] - ); - if ($currentRewrite - && $currentRewrite->getRequestPath() - !== $oldRewrite->getRequestPath() - ) { - return $this->redirect( - $request, - $this->url->getUrl( - '', - ['_direct' => $currentRewrite->getRequestPath()] - ), - OptionProvider::TEMPORARY - ); - } - } - } - $rewrite = $this->getRewrite( $request->getPathInfo(), $this->storeManager->getStore()->getId() diff --git a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php new file mode 100644 index 0000000000000..2ce00d53588b3 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\UrlRewrite\Model\StoreSwitcher; + +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreSwitcherInterface; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * Handle url rewrites for redirect url + */ +class RewriteUrl implements StoreSwitcherInterface +{ + /** + * @var UrlFinderInterface + */ + private $urlFinder; + + /** + * @var \Magento\Framework\HTTP\PhpEnvironment\RequestFactory + */ + private $requestFactory; + + /** + * @param UrlFinderInterface $urlFinder + * @param \Magento\Framework\HTTP\PhpEnvironment\RequestFactory $requestFactory + */ + public function __construct( + UrlFinderInterface $urlFinder, + \Magento\Framework\HTTP\PhpEnvironment\RequestFactory $requestFactory + ) { + $this->urlFinder = $urlFinder; + $this->requestFactory = $requestFactory; + } + + /** + * @param StoreInterface $fromStore + * @param StoreInterface $targetStore + * @param string $redirectUrl + * @return string + */ + public function switch(StoreInterface $fromStore, StoreInterface $targetStore, string $redirectUrl): string + { + $targetUrl = $redirectUrl; + /** @var \Magento\Framework\HTTP\PhpEnvironment\Request $request */ + $request = $this->requestFactory->create(['uri' => $targetUrl]); + + $urlPath = ltrim($request->getPathInfo(), '/'); + + if ($targetStore->isUseStoreInUrl()) { + // Remove store code in redirect url for correct rewrite search + $storeCode = preg_quote($targetStore->getCode() . '/', '/'); + $pattern = "@^($storeCode)@"; + $urlPath = preg_replace($pattern, '', $urlPath); + } + + $oldStoreId = $fromStore->getId(); + $oldRewrite = $this->urlFinder->findOneByData([ + UrlRewrite::REQUEST_PATH => $urlPath, + UrlRewrite::STORE_ID => $oldStoreId, + ]); + if ($oldRewrite) { + // look for url rewrite match on the target store + $currentRewrite = $this->urlFinder->findOneByData([ + UrlRewrite::REQUEST_PATH => $urlPath, + UrlRewrite::STORE_ID => $targetStore->getId(), + ]); + if (null === $currentRewrite) { + /** @var \Magento\Framework\App\Response\Http $response */ + $targetUrl = $targetStore->getBaseUrl(); + } + } + + return $targetUrl; + } +} diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/composer.json b/app/code/Magento/UrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index 2d906c3499ac6..0000000000000 --- a/app/code/Magento/UrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-cms-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php deleted file mode 100644 index 46bf90493004d..0000000000000 --- a/app/code/Magento/UrlRewrite/Test/Unit/Block/Plugin/Store/Switcher/SetRedirectUrlTest.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\UrlRewrite\Test\Unit\Block\Plugin\Store\Switcher; - -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; - -class SetRedirectUrlTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\UrlRewrite\Block\Plugin\Store\Switcher\SetRedirectUrl */ - protected $unit; - - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $urlFinder; - - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $urlHelper; - - /** @var \Magento\Store\Block\Switcher|\PHPUnit_Framework_MockObject_MockObject */ - protected $switcher; - - /** @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject */ - protected $store; - - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $request; - - /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlBuilder; - - protected function setUp() - { - $this->store = $this->createMock(\Magento\Store\Model\Store::class); - $this->request = $this->createMock(\Magento\Framework\App\Request\Http::class); - $this->urlBuilder = $this->createMock(\Magento\Framework\UrlInterface::class); - $this->urlHelper = $this->createMock(\Magento\Framework\Url\Helper\Data::class); - $this->urlFinder = $this->createMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); - $this->switcher = $this->createMock(\Magento\Store\Block\Switcher::class); - - $this->unit = (new ObjectManager($this))->getObject( - \Magento\UrlRewrite\Block\Plugin\Store\Switcher\SetRedirectUrl::class, - [ - 'urlFinder' => $this->urlFinder, - 'urlHelper' => $this->urlHelper, - 'urlBuilder' => $this->urlBuilder, - 'request' => $this->request, - ] - ); - } - - public function testNoUrlRewriteForSpecificStoreOnGetTargetStorePostData() - { - $this->request->expects($this->once())->method('getPathInfo')->willReturn('path'); - $this->urlFinder->expects($this->once())->method('findOneByData')->willReturn(null); - $this->urlHelper->expects($this->never())->method('getEncodedUrl'); - $this->assertEquals( - [$this->store, []], - $this->unit->beforeGetTargetStorePostData($this->switcher, $this->store, []) - ); - } - - public function testTrimPathInfoForGetTargetStorePostData() - { - $this->request->expects($this->once())->method('getPathInfo')->willReturn('path/with/trim/'); - $this->store->expects($this->once())->method('getId')->willReturn(1); - $this->urlFinder->expects($this->once())->method('findOneByData') - ->with([ - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::TARGET_PATH => 'path/with/trim', - \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::STORE_ID => 1, - ]) - ->willReturn(null); - $this->urlHelper->expects($this->never())->method('getEncodedUrl'); - $this->assertEquals( - [$this->store, []], - $this->unit->beforeGetTargetStorePostData($this->switcher, $this->store, []) - ); - } - - public function testGetTargetStorePostData() - { - $urlRewrite = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); - $urlRewrite->expects($this->once())->method('getRequestPath')->willReturn('path'); - - $this->request->expects($this->once())->method('getPathInfo')->willReturn('path'); - $this->urlFinder->expects($this->once())->method('findOneByData')->willReturn($urlRewrite); - $this->urlHelper->expects($this->once())->method('getEncodedUrl')->willReturn('encoded-path'); - $this->urlBuilder->expects($this->once())->method('getUrl')->willReturn('path'); - $this->assertEquals( - [$this->store, [\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => 'encoded-path']], - $this->unit->beforeGetTargetStorePostData($this->switcher, $this->store, []) - ); - } -} diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index 49300448146f2..642ca0f9af6d1 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -6,9 +6,10 @@ namespace Magento\UrlRewrite\Test\Unit\Controller; +use Magento\Framework\App\Action\Forward; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\Store\Model\Store; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -39,6 +40,9 @@ class RouterTest extends \PHPUnit\Framework\TestCase /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $urlFinder; + /** + * @return void + */ protected function setUp() { $this->actionFactory = $this->createMock(\Magento\Framework\App\ActionFactory::class); @@ -67,6 +71,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testNoRewriteExist() { $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue(null)); @@ -76,6 +83,81 @@ public function testNoRewriteExist() $this->assertNull($this->router->match($this->request)); } + /** + * @return void + */ + public function testRewriteAfterStoreSwitcher() + { + $initialRequestPath = 'request-path'; + $newRequestPath = 'new-request-path'; + $oldStoreAlias = 'old-store'; + $oldStoreId = 'old-store-id'; + $currentStoreId = 'current-store-id'; + $rewriteEntityType = 'entity-type'; + $rewriteEntityId = 42; + $this->request + ->expects($this->any()) + ->method('getParam') + ->with('___from_store') + ->willReturn($oldStoreAlias); + $this->request + ->expects($this->any()) + ->method('getPathInfo') + ->willReturn($initialRequestPath); + $oldStore = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $oldStore->expects($this->any()) + ->method('getId') + ->willReturn($oldStoreId); + $this->store + ->expects($this->any()) + ->method('getId') + ->willReturn($currentStoreId); + $this->storeManager + ->expects($this->any()) + ->method('getStore') + ->willReturnMap([[$oldStoreAlias, $oldStore], [null, $this->store]]); + $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) + ->disableOriginalConstructor() + ->getMock(); + $oldUrlRewrite->expects($this->any()) + ->method('getEntityType') + ->willReturn($rewriteEntityType); + $oldUrlRewrite->expects($this->any()) + ->method('getEntityId') + ->willReturn($rewriteEntityId); + $oldUrlRewrite->expects($this->any()) + ->method('getRedirectType') + ->willReturn(0); + $urlRewrite = $this->getMockBuilder(UrlRewrite::class) + ->disableOriginalConstructor() + ->getMock(); + $urlRewrite->expects($this->any()) + ->method('getRequestPath') + ->willReturn($newRequestPath); + $this->urlFinder + ->expects($this->any()) + ->method('findOneByData') + ->willReturnMap([ + [ + [ + UrlRewrite::REQUEST_PATH => $initialRequestPath, + UrlRewrite::STORE_ID => $currentStoreId, + ], + $urlRewrite, + ] + ]); + $this->actionFactory + ->expects($this->once()) + ->method('create') + ->with(Forward::class); + $this->router->match($this->request); + } + + /** + * @return void + */ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() { $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); @@ -98,6 +180,9 @@ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() $this->assertNull($this->router->match($this->request)); } + /** + * @return void + */ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() { $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); @@ -138,6 +223,9 @@ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() $this->assertNull($this->router->match($this->request)); } + /** + * @return void + */ public function testMatchWithRedirect() { $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); @@ -157,6 +245,9 @@ public function testMatchWithRedirect() $this->router->match($this->request); } + /** + * @return void + */ public function testMatchWithCustomInternalRedirect() { $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); @@ -208,6 +299,9 @@ public function externalRedirectTargetPathDataProvider() ]; } + /** + * @return void + */ public function testMatch() { $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); diff --git a/app/code/Magento/UrlRewrite/etc/adminhtml/menu.xml b/app/code/Magento/UrlRewrite/etc/adminhtml/menu.xml index 615e17dbb7af8..66ae94dac1af1 100644 --- a/app/code/Magento/UrlRewrite/etc/adminhtml/menu.xml +++ b/app/code/Magento/UrlRewrite/etc/adminhtml/menu.xml @@ -7,8 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd"> <menu> - <add id="Magento_UrlRewrite::urlrewrite" title="URL Rewrites" translate="title" module="Magento_UrlRewrite" - sortOrder="20" parent="Magento_Backend::marketing_seo" - action="adminhtml/url_rewrite/index" resource="Magento_UrlRewrite::urlrewrite"/> + <add id="Magento_UrlRewrite::urlrewrite" title="URL Rewrites" translate="title" module="Magento_UrlRewrite" sortOrder="20" parent="Magento_Backend::marketing_seo" action="adminhtml/url_rewrite/index" resource="Magento_UrlRewrite::urlrewrite"/> </menu> </config> diff --git a/app/code/Magento/UrlRewrite/etc/db_schema_whitelist.json b/app/code/Magento/UrlRewrite/etc/db_schema_whitelist.json index 24db0162e0394..bdaed647587a6 100644 --- a/app/code/Magento/UrlRewrite/etc/db_schema_whitelist.json +++ b/app/code/Magento/UrlRewrite/etc/db_schema_whitelist.json @@ -1,24 +1,24 @@ { - "url_rewrite": { - "column": { - "url_rewrite_id": true, - "entity_type": true, - "entity_id": true, - "request_path": true, - "target_path": true, - "redirect_type": true, - "store_id": true, - "description": true, - "is_autogenerated": true, - "metadata": true - }, - "index": { - "URL_REWRITE_TARGET_PATH": true, - "URL_REWRITE_STORE_ID_ENTITY_ID": true - }, - "constraint": { - "PRIMARY": true, - "URL_REWRITE_REQUEST_PATH_STORE_ID": true + "url_rewrite": { + "column": { + "url_rewrite_id": true, + "entity_type": true, + "entity_id": true, + "request_path": true, + "target_path": true, + "redirect_type": true, + "store_id": true, + "description": true, + "is_autogenerated": true, + "metadata": true + }, + "index": { + "URL_REWRITE_TARGET_PATH": true, + "URL_REWRITE_STORE_ID_ENTITY_ID": true + }, + "constraint": { + "PRIMARY": true, + "URL_REWRITE_REQUEST_PATH_STORE_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/UrlRewrite/etc/di.xml b/app/code/Magento/UrlRewrite/etc/di.xml index dfeb5d026d508..26055efbd2ba8 100644 --- a/app/code/Magento/UrlRewrite/etc/di.xml +++ b/app/code/Magento/UrlRewrite/etc/di.xml @@ -9,4 +9,11 @@ <preference for="Magento\UrlRewrite\Model\StorageInterface" type="Magento\UrlRewrite\Model\Storage\DbStorage"/> <preference for="Magento\UrlRewrite\Model\UrlFinderInterface" type="Magento\UrlRewrite\Model\Storage\DbStorage"/> <preference for="Magento\UrlRewrite\Model\UrlPersistInterface" type="Magento\UrlRewrite\Model\Storage\DbStorage"/> + <type name="Magento\Store\Model\StoreSwitcher"> + <arguments> + <argument name="storeSwitchers" xsi:type="array"> + <item name="rewriteUrl" xsi:type="object">Magento\UrlRewrite\Model\StoreSwitcher\RewriteUrl</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/UrlRewrite/etc/frontend/di.xml b/app/code/Magento/UrlRewrite/etc/frontend/di.xml index d46bb1c5d14a2..bc5b6aa767fa8 100644 --- a/app/code/Magento/UrlRewrite/etc/frontend/di.xml +++ b/app/code/Magento/UrlRewrite/etc/frontend/di.xml @@ -17,7 +17,4 @@ </argument> </arguments> </type> - <type name="Magento\Store\Block\Switcher"> - <plugin name="setStoreSpecificRedirectUrl" type="Magento\UrlRewrite\Block\Plugin\Store\Switcher\SetRedirectUrl"/> - </type> </config> diff --git a/app/code/Magento/UrlRewriteGraphQl/Test/Mftf/composer.json b/app/code/Magento/UrlRewriteGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 06c58dbb59468..0000000000000 --- a/app/code/Magento/UrlRewriteGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-url-rewrite-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/User/Block/Role/Tab/Edit.php b/app/code/Magento/User/Block/Role/Tab/Edit.php index 45d725c61bd52..5fe6a1b2a2e88 100644 --- a/app/code/Magento/User/Block/Role/Tab/Edit.php +++ b/app/code/Magento/User/Block/Role/Tab/Edit.php @@ -19,7 +19,7 @@ class Edit extends \Magento\Backend\Block\Widget\Form implements \Magento\Backen /** * @var string */ - protected $_template = 'role/edit.phtml'; + protected $_template = 'Magento_User::role/edit.phtml'; /** * Root ACL Resource diff --git a/app/code/Magento/User/Block/Role/Tab/Users.php b/app/code/Magento/User/Block/Role/Tab/Users.php index 27604c9aed29a..a95a68cbe14f3 100644 --- a/app/code/Magento/User/Block/Role/Tab/Users.php +++ b/app/code/Magento/User/Block/Role/Tab/Users.php @@ -45,7 +45,9 @@ protected function _construct() $roleId = $this->getRequest()->getParam('rid', false); /** @var \Magento\User\Model\ResourceModel\User\Collection $users */ $users = $this->_userCollectionFactory->create()->load(); - $this->setTemplate('role/users.phtml')->assign('users', $users->getItems())->assign('roleId', $roleId); + $this->setTemplate('Magento_User::role/users.phtml') + ->assign('users', $users->getItems()) + ->assign('roleId', $roleId); } /** diff --git a/app/code/Magento/User/Controller/Adminhtml/Auth.php b/app/code/Magento/User/Controller/Adminhtml/Auth.php index 173fdcc764f6f..ea539b64a5cc3 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Auth.php +++ b/app/code/Magento/User/Controller/Adminhtml/Auth.php @@ -7,28 +7,32 @@ namespace Magento\User\Controller\Adminhtml; use Magento\Framework\Encryption\Helper\Security; +use Magento\Backend\App\AbstractAction; +use Magento\Backend\App\Action\Context; +use Magento\User\Model\UserFactory; +use Magento\Framework\Exception\LocalizedException; /** * \Magento\User Auth controller */ -abstract class Auth extends \Magento\Backend\App\AbstractAction +abstract class Auth extends AbstractAction { /** * User model factory * - * @var \Magento\User\Model\UserFactory + * @var UserFactory */ protected $_userFactory; /** * Construct * - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\User\Model\UserFactory $userFactory + * @param Context $context + * @param UserFactory $userFactory */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\User\Model\UserFactory $userFactory + Context $context, + UserFactory $userFactory ) { parent::__construct($context); $this->_userFactory = $userFactory; @@ -40,7 +44,7 @@ public function __construct( * @param int $userId * @param string $resetPasswordToken * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ protected function _validateResetPasswordLinkToken($userId, $resetPasswordToken) { @@ -50,22 +54,20 @@ protected function _validateResetPasswordLinkToken($userId, $resetPasswordToken) $resetPasswordToken ) || empty($resetPasswordToken) || empty($userId) || $userId < 0 ) { - throw new \Magento\Framework\Exception\LocalizedException( - __('The password reset token is incorrect. Verify the token and try again.') - ); + throw new LocalizedException(__('Please correct the password reset token.')); } /** @var $user \Magento\User\Model\User */ $user = $this->_userFactory->create()->load($userId); if (!$user->getId()) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Please specify the correct account and try again.') ); } $userToken = $user->getRpToken(); if (!Security::compareStrings($userToken, $resetPasswordToken) || $user->isResetPasswordLinkTokenExpired()) { - throw new \Magento\Framework\Exception\LocalizedException(__('Your password reset link has expired.')); + throw new LocalizedException(__('Your password reset link has expired.')); } } diff --git a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php index cd4c3d6950685..c0e78fae2c2c6 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php +++ b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php @@ -7,8 +7,17 @@ namespace Magento\User\Controller\Adminhtml\Auth; use Magento\Security\Model\SecurityManager; +use Magento\Framework\App\ObjectManager; +use Magento\Backend\App\Action\Context; +use Magento\User\Model\UserFactory; +use Magento\User\Model\ResourceModel\User\CollectionFactory; +use Magento\Framework\Validator\EmailAddress; +use Magento\Security\Model\PasswordResetRequestEvent; +use Magento\Framework\Exception\SecurityViolationException; +use Magento\User\Controller\Adminhtml\Auth; +use Magento\Backend\Helper\Data; -class Forgotpassword extends \Magento\User\Controller\Adminhtml\Auth +class Forgotpassword extends Auth { /** * @var SecurityManager @@ -16,17 +25,36 @@ class Forgotpassword extends \Magento\User\Controller\Adminhtml\Auth protected $securityManager; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\User\Model\UserFactory $userFactory - * @param \Magento\Security\Model\SecurityManager $securityManager + * User model factory + * + * @var CollectionFactory + */ + private $userCollectionFactory; + + /** + * @var Data + */ + private $backendDataHelper; + + /** + * @param Context $context + * @param UserFactory $userFactory + * @param SecurityManager $securityManager + * @param CollectionFactory $userCollectionFactory */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\User\Model\UserFactory $userFactory, - \Magento\Security\Model\SecurityManager $securityManager + Context $context, + UserFactory $userFactory, + SecurityManager $securityManager, + CollectionFactory $userCollectionFactory = null, + Data $backendDataHelper = null ) { parent::__construct($context, $userFactory); $this->securityManager = $securityManager; + $this->userCollectionFactory = $userCollectionFactory ?: + ObjectManager::getInstance()->get(CollectionFactory::class); + $this->backendDataHelper = $backendDataHelper ?: + ObjectManager::getInstance()->get(Data::class); } /** @@ -44,18 +72,18 @@ public function execute() $resultRedirect = $this->resultRedirectFactory->create(); if (!empty($email) && !empty($params)) { // Validate received data to be an email address - if (\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) { + if (\Zend_Validate::is($email, EmailAddress::class)) { try { $this->securityManager->performSecurityCheck( - \Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST, + PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST, $email ); - } catch (\Magento\Framework\Exception\SecurityViolationException $exception) { + } catch (SecurityViolationException $exception) { $this->messageManager->addErrorMessage($exception->getMessage()); return $resultRedirect->setPath('admin'); } - $collection = $this->_objectManager->get(\Magento\User\Model\ResourceModel\User\Collection::class); /** @var $collection \Magento\User\Model\ResourceModel\User\Collection */ + $collection = $this->userCollectionFactory->create(); $collection->addFieldToFilter('email', $email); $collection->load(false); @@ -65,9 +93,7 @@ public function execute() /** @var \Magento\User\Model\User $user */ $user = $this->_userFactory->create()->load($item->getId()); if ($user->getId()) { - $newPassResetToken = $this->_objectManager->get( - \Magento\User\Helper\Data::class - )->generateResetPasswordLinkToken(); + $newPassResetToken = $this->backendDataHelper->generateResetPasswordLinkToken(); $user->changeResetPasswordLinkToken($newPassResetToken); $user->save(); $user->sendPasswordResetConfirmationEmail(); @@ -86,7 +112,7 @@ public function execute() $this->messageManager->addSuccess(__('We\'ll email you a link to reset your password.')); // @codingStandardsIgnoreEnd $this->getResponse()->setRedirect( - $this->_objectManager->get(\Magento\Backend\Helper\Data::class)->getHomePageUrl() + $this->backendDataHelper->getHomePageUrl() ); return; } else { diff --git a/app/code/Magento/User/Controller/Adminhtml/Auth/ResetPasswordPost.php b/app/code/Magento/User/Controller/Adminhtml/Auth/ResetPasswordPost.php index 2c6be98439919..c2e29534b1251 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Auth/ResetPasswordPost.php +++ b/app/code/Magento/User/Controller/Adminhtml/Auth/ResetPasswordPost.php @@ -6,8 +6,32 @@ */ namespace Magento\User\Controller\Adminhtml\Auth; -class ResetPasswordPost extends \Magento\User\Controller\Adminhtml\Auth +use Magento\User\Controller\Adminhtml\Auth; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\ObjectManager; +use Magento\Backend\Helper\Data; +use Magento\User\Model\UserFactory; + +class ResetPasswordPost extends Auth { + /** + * @var Data + */ + private $backendDataHelper; + + /** + * @param Context $context + * @param UserFactory $userFactory + * @param Data $backendDataHelper + */ + public function __construct( + Context $context, + UserFactory $userFactory, + Data $backendDataHelper = null + ) { + parent::__construct($context, $userFactory); + $this->backendDataHelper = $backendDataHelper ?: ObjectManager::getInstance()->get(Data::class); + } /** * Reset forgotten password * @@ -27,7 +51,7 @@ public function execute() } catch (\Exception $exception) { $this->messageManager->addError(__('Your password reset link has expired.')); $this->getResponse()->setRedirect( - $this->_objectManager->get(\Magento\Backend\Helper\Data::class)->getHomePageUrl() + $this->backendDataHelper->getHomePageUrl() ); return; } @@ -53,7 +77,7 @@ public function execute() $user->save(); $this->messageManager->addSuccess(__('You updated your password.')); $this->getResponse()->setRedirect( - $this->_objectManager->get(\Magento\Backend\Helper\Data::class)->getHomePageUrl() + $this->backendDataHelper->getHomePageUrl() ); } } catch (\Magento\Framework\Validator\Exception $exception) { diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml index e8aff30ff8d67..de887d2de6704 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminCreateUserActionGroup"> <arguments> <argument name="role"/> diff --git a/app/code/Magento/User/Test/Mftf/Data/UserData.xml b/app/code/Magento/User/Test/Mftf/Data/UserData.xml index 93bf0ccfa43d6..03ae3dba21840 100644 --- a/app/code/Magento/User/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/User/Test/Mftf/Data/UserData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="admin" type="user"> <data key="email">admin@magento.com</data> <data key="password">admin123</data> diff --git a/app/code/Magento/User/Test/Mftf/Data/UserRoleData.xml b/app/code/Magento/User/Test/Mftf/Data/UserRoleData.xml index 39eea63356b7e..641b692adea5c 100644 --- a/app/code/Magento/User/Test/Mftf/Data/UserRoleData.xml +++ b/app/code/Magento/User/Test/Mftf/Data/UserRoleData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="adminRole" type="role"> <data key="name" unique="suffix">adminRole</data> <data key="scope">1</data> diff --git a/app/code/Magento/User/Test/Mftf/Page/AdminEditRolePage.xml b/app/code/Magento/User/Test/Mftf/Page/AdminEditRolePage.xml index 8c71a815a3346..5b94553e398c5 100644 --- a/app/code/Magento/User/Test/Mftf/Page/AdminEditRolePage.xml +++ b/app/code/Magento/User/Test/Mftf/Page/AdminEditRolePage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminEditRolePage" url="admin/user_role/editrole" module="Magento_User" area="admin"> <section name="AdminEditRoleInfoSection"/> </page> diff --git a/app/code/Magento/User/Test/Mftf/Page/AdminEditUserPage.xml b/app/code/Magento/User/Test/Mftf/Page/AdminEditUserPage.xml index 7f6751c6f9e9b..ae965fa1c48e7 100644 --- a/app/code/Magento/User/Test/Mftf/Page/AdminEditUserPage.xml +++ b/app/code/Magento/User/Test/Mftf/Page/AdminEditUserPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminEditUserPage" url="admin/user/new" area="admin" module="Magento_User"> <section name="AdminEditUserSection"/> <section name="AdminEditUserRoleSection"/> diff --git a/app/code/Magento/User/Test/Mftf/Page/AdminRolesPage.xml b/app/code/Magento/User/Test/Mftf/Page/AdminRolesPage.xml index 87cf0625670e0..e3b0c55f99cc1 100644 --- a/app/code/Magento/User/Test/Mftf/Page/AdminRolesPage.xml +++ b/app/code/Magento/User/Test/Mftf/Page/AdminRolesPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminRolesPage" url="admin/user_role/" module="Magento_User" area="admin"> <section name="AdminRoleGridSection"/> </page> diff --git a/app/code/Magento/User/Test/Mftf/Page/AdminUsersPage.xml b/app/code/Magento/User/Test/Mftf/Page/AdminUsersPage.xml index 2075cba2bdccb..ceb05ec7bd9c8 100644 --- a/app/code/Magento/User/Test/Mftf/Page/AdminUsersPage.xml +++ b/app/code/Magento/User/Test/Mftf/Page/AdminUsersPage.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminUsersPage" url="admin/user/" area="admin" module="Magento_User"> <section name="AdminUserGridSection"/> </page> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml index feb7b3e3bba8b..e30a545649d12 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditRoleInfoSection"> <element name="roleName" type="input" selector="#role_name"/> <element name="password" type="input" selector="#current_password"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserRoleSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserRoleSection.xml index dc12205a84d9a..8f6f2352ff01b 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserRoleSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserRoleSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditUserRoleSection"> <element name="usernameTextField" type="input" selector="#user_username"/> <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml index 1406758b86118..5b866b45e2fbe 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditUserSection"> <element name="usernameTextField" type="input" selector="#user_username"/> <element name="firstNameTextField" type="input" selector="#user_firstname"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml index 1e1f3457995fe..6db6858500342 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminRoleGridSection"> <element name="idFilterTextField" type="input" selector="#roleGrid_filter_role_id"/> <element name="roleNameFilterTextField" type="input" selector="#roleGrid_filter_role_name"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml index b6d2645ac7384..f429c390efe6b 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminUserGridSection"> <element name="usernameFilterTextField" type="input" selector="#permissionsUserGrid_filter_username"/> <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> diff --git a/app/code/Magento/User/Test/Mftf/composer.json b/app/code/Magento/User/Test/Mftf/composer.json deleted file mode 100644 index 6deceeb6b856f..0000000000000 --- a/app/code/Magento/User/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-user", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/User/Test/Unit/Model/ResourceModel/UserTest.php b/app/code/Magento/User/Test/Unit/Model/ResourceModel/UserTest.php index 16cde3cfe2c06..1cc0cd54c60eb 100644 --- a/app/code/Magento/User/Test/Unit/Model/ResourceModel/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/ResourceModel/UserTest.php @@ -330,7 +330,7 @@ public function testDeleteFromRole() $roleId = 44; $methodUserMock->expects($this->once())->method('getUserId')->willReturn($uid); $this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->dbAdapterMock); - $methodUserMock->expects($this->atleastOnce())->method('getRoleId')->willReturn($roleId); + $methodUserMock->expects($this->atLeastOnce())->method('getRoleId')->willReturn($roleId); $this->dbAdapterMock->expects($this->once())->method('delete'); $this->assertInstanceOf( diff --git a/app/code/Magento/User/etc/db_schema_whitelist.json b/app/code/Magento/User/etc/db_schema_whitelist.json index 1ae7f78de0218..2af77c0d8455b 100644 --- a/app/code/Magento/User/etc/db_schema_whitelist.json +++ b/app/code/Magento/User/etc/db_schema_whitelist.json @@ -1,45 +1,45 @@ { - "admin_user": { - "column": { - "user_id": true, - "firstname": true, - "lastname": true, - "email": true, - "username": true, - "password": true, - "created": true, - "modified": true, - "logdate": true, - "lognum": true, - "reload_acl_flag": true, - "is_active": true, - "extra": true, - "rp_token": true, - "rp_token_created_at": true, - "interface_locale": true, - "failures_num": true, - "first_failure": true, - "lock_expires": true + "admin_user": { + "column": { + "user_id": true, + "firstname": true, + "lastname": true, + "email": true, + "username": true, + "password": true, + "created": true, + "modified": true, + "logdate": true, + "lognum": true, + "reload_acl_flag": true, + "is_active": true, + "extra": true, + "rp_token": true, + "rp_token_created_at": true, + "interface_locale": true, + "failures_num": true, + "first_failure": true, + "lock_expires": true + }, + "constraint": { + "PRIMARY": true, + "ADMIN_USER_USERNAME": true + } }, - "constraint": { - "PRIMARY": true, - "ADMIN_USER_USERNAME": true + "admin_passwords": { + "column": { + "password_id": true, + "user_id": true, + "password_hash": true, + "expires": true, + "last_updated": true + }, + "index": { + "ADMIN_PASSWORDS_USER_ID": true + }, + "constraint": { + "PRIMARY": true, + "ADMIN_PASSWORDS_USER_ID_ADMIN_USER_USER_ID": true + } } - }, - "admin_passwords": { - "column": { - "password_id": true, - "user_id": true, - "password_hash": true, - "expires": true, - "last_updated": true - }, - "index": { - "ADMIN_PASSWORDS_USER_ID": true - }, - "constraint": { - "PRIMARY": true, - "ADMIN_PASSWORDS_USER_ID_ADMIN_USER_USER_ID": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Usps/Test/Mftf/composer.json b/app/code/Magento/Usps/Test/Mftf/composer.json deleted file mode 100644 index e3b487ad80d9f..0000000000000 --- a/app/code/Magento/Usps/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-usps", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Variable/Test/Mftf/ActionGroup/CreateCustomVariableActionGroup.xml b/app/code/Magento/Variable/Test/Mftf/ActionGroup/CreateCustomVariableActionGroup.xml index 0df617c876d8e..610676b350455 100644 --- a/app/code/Magento/Variable/Test/Mftf/ActionGroup/CreateCustomVariableActionGroup.xml +++ b/app/code/Magento/Variable/Test/Mftf/ActionGroup/CreateCustomVariableActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateCustomVariableActionGroup"> <amOnPage url="admin/admin/system_variable/new/" stepKey="goToNewCustomVarialePage" /> <waitForPageLoad stepKey="waitForPageLoad" /> diff --git a/app/code/Magento/Variable/Test/Mftf/Data/VariableData.xml b/app/code/Magento/Variable/Test/Mftf/Data/VariableData.xml index 9038030b30cbf..7b7fd768f0ab1 100644 --- a/app/code/Magento/Variable/Test/Mftf/Data/VariableData.xml +++ b/app/code/Magento/Variable/Test/Mftf/Data/VariableData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="_defaultVariable" type="cms_page"> <data key="city"> Austin </data> </entity> diff --git a/app/code/Magento/Variable/Test/Mftf/composer.json b/app/code/Magento/Variable/Test/Mftf/composer.json deleted file mode 100644 index 2360634369fed..0000000000000 --- a/app/code/Magento/Variable/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-variable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Variable/etc/db_schema_whitelist.json b/app/code/Magento/Variable/etc/db_schema_whitelist.json index f039ad3a87a37..b122e33eceb19 100644 --- a/app/code/Magento/Variable/etc/db_schema_whitelist.json +++ b/app/code/Magento/Variable/etc/db_schema_whitelist.json @@ -1,31 +1,31 @@ { - "variable": { - "column": { - "variable_id": true, - "code": true, - "name": true + "variable": { + "column": { + "variable_id": true, + "code": true, + "name": true + }, + "constraint": { + "PRIMARY": true, + "VARIABLE_CODE": true + } }, - "constraint": { - "PRIMARY": true, - "VARIABLE_CODE": true + "variable_value": { + "column": { + "value_id": true, + "variable_id": true, + "store_id": true, + "plain_value": true, + "html_value": true + }, + "index": { + "VARIABLE_VALUE_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "VARIABLE_VALUE_VARIABLE_ID_STORE_ID": true, + "VARIABLE_VALUE_STORE_ID_STORE_STORE_ID": true, + "VARIABLE_VALUE_VARIABLE_ID_VARIABLE_VARIABLE_ID": true + } } - }, - "variable_value": { - "column": { - "value_id": true, - "variable_id": true, - "store_id": true, - "plain_value": true, - "html_value": true - }, - "index": { - "VARIABLE_VALUE_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "VARIABLE_VALUE_VARIABLE_ID_STORE_ID": true, - "VARIABLE_VALUE_STORE_ID_STORE_STORE_ID": true, - "VARIABLE_VALUE_VARIABLE_ID_VARIABLE_VARIABLE_ID": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Vault/Test/Mftf/composer.json b/app/code/Magento/Vault/Test/Mftf/composer.json deleted file mode 100644 index 78e765bebc429..0000000000000 --- a/app/code/Magento/Vault/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-vault", - "description": "", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index a4093617b82cc..f888a98692288 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -16,7 +16,8 @@ }, "type": "magento2-module", "license": [ - "proprietary" + "OSL-3.0", + "AFL-3.0" ], "autoload": { "files": [ diff --git a/app/code/Magento/Vault/etc/db_schema_whitelist.json b/app/code/Magento/Vault/etc/db_schema_whitelist.json index 9b5e740fd16e8..e2275dc3532d7 100644 --- a/app/code/Magento/Vault/etc/db_schema_whitelist.json +++ b/app/code/Magento/Vault/etc/db_schema_whitelist.json @@ -1,36 +1,36 @@ { - "vault_payment_token": { - "column": { - "entity_id": true, - "customer_id": true, - "public_hash": true, - "payment_method_code": true, - "type": true, - "created_at": true, - "expires_at": true, - "gateway_token": true, - "details": true, - "is_active": true, - "is_visible": true + "vault_payment_token": { + "column": { + "entity_id": true, + "customer_id": true, + "public_hash": true, + "payment_method_code": true, + "type": true, + "created_at": true, + "expires_at": true, + "gateway_token": true, + "details": true, + "is_active": true, + "is_visible": true + }, + "constraint": { + "PRIMARY": true, + "VAULT_PAYMENT_TOKEN_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "VAULT_PAYMENT_TOKEN_PAYMENT_METHOD_CODE_CSTR_ID_GATEWAY_TOKEN": true, + "UNQ_54DCE14AEAEA03B587F9EF723EB10A10": true, + "VAULT_PAYMENT_TOKEN_PUBLIC_HASH": true, + "VAULT_PAYMENT_TOKEN_HASH_UNIQUE_INDEX_PUBLIC_HASH": true + } }, - "constraint": { - "PRIMARY": true, - "VAULT_PAYMENT_TOKEN_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "VAULT_PAYMENT_TOKEN_PAYMENT_METHOD_CODE_CSTR_ID_GATEWAY_TOKEN": true, - "UNQ_54DCE14AEAEA03B587F9EF723EB10A10": true, - "VAULT_PAYMENT_TOKEN_PUBLIC_HASH": true, - "VAULT_PAYMENT_TOKEN_HASH_UNIQUE_INDEX_PUBLIC_HASH": true + "vault_payment_token_order_payment_link": { + "column": { + "order_payment_id": true, + "payment_token_id": true + }, + "constraint": { + "PRIMARY": true, + "FK_CF37B9D854256534BE23C818F6291CA2": true, + "FK_4ED894655446D385894580BECA993862": true + } } - }, - "vault_payment_token_order_payment_link": { - "column": { - "order_payment_id": true, - "payment_token_id": true - }, - "constraint": { - "PRIMARY": true, - "FK_CF37B9D854256534BE23C818F6291CA2": true, - "FK_4ED894655446D385894580BECA993862": true - } - } -} +} \ No newline at end of file diff --git a/app/code/Magento/Version/Test/Mftf/composer.json b/app/code/Magento/Version/Test/Mftf/composer.json deleted file mode 100644 index 24037307b7878..0000000000000 --- a/app/code/Magento/Version/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-version", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Webapi/Test/Mftf/composer.json b/app/code/Magento/Webapi/Test/Mftf/composer.json deleted file mode 100644 index 28d5769034a39..0000000000000 --- a/app/code/Magento/Webapi/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-webapi", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/WebapiAsync/Test/Mftf/composer.json b/app/code/Magento/WebapiAsync/Test/Mftf/composer.json deleted file mode 100644 index 6bf5375b7c873..0000000000000 --- a/app/code/Magento/WebapiAsync/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-webapi-async", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/functional-test-module-asynchronous-operations": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json b/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json deleted file mode 100644 index de96b3a3210f0..0000000000000 --- a/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-webapi-security", - "description": "WebapiSecurity module provides option to loosen security on some webapi resources.", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-webapi": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml b/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml index 425a2047cfd51..d6f40f5ac2023 100644 --- a/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml +++ b/app/code/Magento/WebapiSecurity/etc/adminhtml/system.xml @@ -5,10 +5,10 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> <system> - <section id="webapi" translate="label" type="text" sortOrder="102" showInDefault="1" showInWebsite="1" showInStore="1"> + <section id="webapi" type="text" sortOrder="102" showInDefault="1" showInWebsite="1" showInStore="1"> <group id="webapisecurity" translate="label" type="text" sortOrder="250" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Web API Security</label> - <field id="allow_insecure" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="allow_insecure" translate="label comment" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Allow Anonymous Guest Access</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment>This feature applies only to CMS, Catalog and Store APIs. Please consult your developers for details on potential security risks.</comment> diff --git a/app/code/Magento/WebapiSecurity/i18n/en_US.csv b/app/code/Magento/WebapiSecurity/i18n/en_US.csv index 94a4e8706bcd8..5e5e821e63e7f 100644 --- a/app/code/Magento/WebapiSecurity/i18n/en_US.csv +++ b/app/code/Magento/WebapiSecurity/i18n/en_US.csv @@ -1,2 +1,3 @@ "Web API Security","Web API Security" "Allow Anonymous Guest Access","Allow Anonymous Guest Access" +"This feature applies only to CMS, Catalog and Store APIs. Please consult your developers for details on potential security risks.","This feature applies only to CMS, Catalog and Store APIs. Please consult your developers for details on potential security risks." diff --git a/app/code/Magento/Weee/Block/Renderer/Weee/Tax.php b/app/code/Magento/Weee/Block/Renderer/Weee/Tax.php index ab6710fddcac2..94a6faf72aa96 100644 --- a/app/code/Magento/Weee/Block/Renderer/Weee/Tax.php +++ b/app/code/Magento/Weee/Block/Renderer/Weee/Tax.php @@ -32,7 +32,7 @@ class Tax extends \Magento\Backend\Block\Widget implements /** * @var string */ - protected $_template = 'renderer/tax.phtml'; + protected $_template = 'Magento_Weee::renderer/tax.phtml'; /** * Core registry diff --git a/app/code/Magento/Weee/Model/Tax.php b/app/code/Magento/Weee/Model/Tax.php index 3c6d29ae75217..941faed0498f4 100644 --- a/app/code/Magento/Weee/Model/Tax.php +++ b/app/code/Magento/Weee/Model/Tax.php @@ -248,10 +248,20 @@ public function getProductWeeeAttributes( $round = true ) { $result = []; - - $websiteId = $this->_storeManager->getWebsite($website)->getId(); + $websiteId = null; /** @var \Magento\Store\Model\Store $store */ - $store = $this->_storeManager->getWebsite($website)->getDefaultGroup()->getDefaultStore(); + $store = null; + if (!$website) { + $store = $product->getStore(); + if ($store) { + $websiteId = $store->getWebsiteId(); + } + } + if (!$websiteId) { + $websiteObject = $this->_storeManager->getWebsite($website); + $websiteId = $websiteObject->getId(); + $store = $websiteObject->getDefaultGroup()->getDefaultStore(); + } $allWeee = $this->getWeeeTaxAttributeCodes($store); if (!$allWeee) { diff --git a/app/code/Magento/Weee/Test/Mftf/ActionGroup/AdminProductAddFPTValueActionGroup.xml b/app/code/Magento/Weee/Test/Mftf/ActionGroup/AdminProductAddFPTValueActionGroup.xml new file mode 100644 index 0000000000000..39deec3d81bac --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/ActionGroup/AdminProductAddFPTValueActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!--Navigate to create product page from product grid page--> + <actionGroup name="AdminProductAddFPTValueActionGroup"> + <arguments> + <argument name="FPTAttributeCode"/> + <argument name="countryForFPT" defaultValue="US" type="string"/> + <argument name="stateForFPT" type="string"/> + <argument name="valueForFPT" type="string"/> + </arguments> + <click selector="{{AdminProductAddFPTValueSection.addFPT(FPTAttributeCode)}}" stepKey="clickAddFPTButton1"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <selectOption selector="{{AdminProductAddFPTValueSection.selectCountryForFPT(FPTAttributeCode)}}" userInput="{{countryForFPT}}" stepKey="selectcountryForFPT"/> + <selectOption selector="{{AdminProductAddFPTValueSection.selectStateForFPT(FPTAttributeCode)}}" userInput="{{stateForFPT}}" stepKey="selectstateForFPT"/> + <fillField selector="{{AdminProductAddFPTValueSection.setTaxValueForFPT(FPTAttributeCode)}}" userInput="{{valueForFPT}}" stepKey="setTaxvalueForFPT"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Weee/Test/Mftf/Data/FixedProductAttributeData.xml b/app/code/Magento/Weee/Test/Mftf/Data/FixedProductAttributeData.xml new file mode 100644 index 0000000000000..b8b45d84242c9 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Data/FixedProductAttributeData.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="productFPTAttribute" type="ProductAttribute"> + <data key="attribute_code" unique="suffix">attribute</data> + <data key="is_unique">true</data> + <data key="frontend_input">weee</data> + <data key="is_used_in_grid">true</data> + <data key="is_visible_in_grid">true</data> + <data key="is_filterable_in_grid">true</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Weee/Test/Mftf/Data/WeeeConfigData.xml b/app/code/Magento/Weee/Test/Mftf/Data/WeeeConfigData.xml new file mode 100644 index 0000000000000..e44c1bb51e41b --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Data/WeeeConfigData.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <!-- Fixed Product Taxes Enable--> + <entity name="WeeeConfigEnable" type="weee_config"> + <requiredEntity type="enableFPT">EnableFPT</requiredEntity> + </entity> + <entity name="EnableFPT" type="enableFPT"> + <data key="value">1</data> + </entity> + <!-- Fixed Product Taxes Disable--> + <entity name="WeeeConfigDisable" type="weee_config_default"> + <requiredEntity type="disableFPT">DisableFPT</requiredEntity> + </entity> + <entity name="DisableFPT" type="disableFPT"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Weee/Test/Mftf/Metadata/weee_config-meta.xml b/app/code/Magento/Weee/Test/Mftf/Metadata/weee_config-meta.xml new file mode 100644 index 0000000000000..56153067658d2 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Metadata/weee_config-meta.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="WeeeConfigEnable" dataType="weee_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/tax/" method="POST"> + <object key="groups" dataType="weee_config"> + <object key="weee" dataType="weee_config"> + <object key="fields" dataType="weee_config"> + <object key="enable" dataType="enableFPT"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> + <operation name="WeeeConfigDefault" dataType="weee_config_default" type="create" auth="adminFormKey" url="/admin/system_config/save/section/tax/" method="POST"> + <object key="groups" dataType="weee_config_default"> + <object key="weee" dataType="weee_config_default"> + <object key="fields" dataType="weee_config_default"> + <object key="enable" dataType="weee_config_default"> + <object key="inherit" dataType="disableFPT"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Weee/Test/Mftf/Page/AdminProductEditPage.xml b/app/code/Magento/Weee/Test/Mftf/Page/AdminProductEditPage.xml new file mode 100644 index 0000000000000..793b763f0fc15 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Page/AdminProductEditPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminProductEditPage" url="catalog/product/edit/id/{{product_id}}/" area="admin" module="Magento_Catalog"> + <section name="AdminProductAddFPTValueSection"/> + </page> +</pages> diff --git a/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml new file mode 100644 index 0000000000000..eee3f421910e1 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminProductAddFPTValueSection"> + <element name="addFPT" type="button" selector="[data-index='{{FPTAttributeCode}}'] [data-action='add_new_row']" parameterized="true"/> + <element name="selectCountryForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[country]')])[last()]" parameterized="true"/> + <element name="selectStateForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[state]')])[last()]" parameterized="true"/> + <element name="setTaxValueForFPT" type="text" selector="(//input[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[value]')])[last()]" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml new file mode 100644 index 0000000000000..2f8ed312f15cf --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CheckoutCartSummarySection"> + <element name="amountFPT" type="text" selector=".totals td[data-th='FPT'] .price"/> + </section> +</sections> diff --git a/app/code/Magento/Weee/Test/Mftf/composer.json b/app/code/Magento/Weee/Test/Mftf/composer.json deleted file mode 100644 index 9e5f7c5772a4e..0000000000000 --- a/app/code/Magento/Weee/Test/Mftf/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "magento/functional-test-module-weee", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-bundle": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Weee/Test/Unit/Model/TaxTest.php b/app/code/Magento/Weee/Test/Unit/Model/TaxTest.php index 7414e0444598b..ac90dfade07e2 100644 --- a/app/code/Magento/Weee/Test/Unit/Model/TaxTest.php +++ b/app/code/Magento/Weee/Test/Unit/Model/TaxTest.php @@ -157,13 +157,17 @@ protected function setUp() } /** - * test GetProductWeeeAttributes * @dataProvider getProductWeeeAttributesDataProvider * @param array $weeeTaxCalculationsByEntity - * @param array $expectedFptLabel + * @param mixed $websitePassed + * @param string $expectedFptLabel + * @return void */ - public function testGetProductWeeeAttributes($weeeTaxCalculationsByEntity, $expectedFptLabel) - { + public function testGetProductWeeeAttributes( + array $weeeTaxCalculationsByEntity, + $websitePassed, + string $expectedFptLabel + ): void { $product = $this->createMock(\Magento\Catalog\Model\Product::class); $website = $this->createMock(\Magento\Store\Model\Website::class); $store = $this->createMock(\Magento\Store\Model\Store::class); @@ -187,28 +191,38 @@ public function testGetProductWeeeAttributes($weeeTaxCalculationsByEntity, $expe ->method('getAttributeCodesByFrontendType') ->willReturn(['0'=>'fpt']); - $store->expects($this->any()) - ->method('getId') - ->willReturn(1); - - $product->expects($this->any()) - ->method('getId') - ->willReturn(1); - + $this->storeManager->expects($this->any()) + ->method('getWebsite') + ->willReturn($website); $website->expects($this->any()) ->method('getId') - ->willReturn(1); + ->willReturn($websitePassed); $website->expects($this->any()) ->method('getDefaultGroup') ->willReturn($group); - $group->expects($this->any()) ->method('getDefaultStore') ->willReturn($store); + $store->expects($this->any()) + ->method('getId') + ->willReturn(1); - $this->storeManager->expects($this->any()) - ->method('getWebsite') - ->willReturn($website); + if ($websitePassed) { + $product->expects($this->never()) + ->method('getStore') + ->willReturn($store); + } else { + $product->expects($this->once()) + ->method('getStore') + ->willReturn($store); + $store->expects($this->once()) + ->method('getWebsiteId') + ->willReturn(1); + } + + $product->expects($this->any()) + ->method('getId') + ->willReturn(1); $this->weeeConfig->expects($this->any()) ->method('isEnabled') @@ -237,7 +251,7 @@ public function testGetProductWeeeAttributes($weeeTaxCalculationsByEntity, $expe 0 => $weeeTaxCalculationsByEntity ]); - $result = $this->model->getProductWeeeAttributes($product, null, null, null, true); + $result = $this->model->getProductWeeeAttributes($product, null, null, $websitePassed, true); $this->assertTrue(is_array($result)); $this->assertArrayHasKey(0, $result); $obj = $result[0]; @@ -312,7 +326,8 @@ public function getProductWeeeAttributesDataProvider() 'frontend_label' => 'fpt_label_frontend', 'attribute_code' => 'fpt_code', ], - 'expectedFptLabel' => 'fpt_label' + 'websitePassed' => 1, + 'expectedFptLabel' => 'fpt_label', ], 'store_label_not_defined' => [ 'weeeTaxCalculationsByEntity' => [ @@ -321,8 +336,19 @@ public function getProductWeeeAttributesDataProvider() 'frontend_label' => 'fpt_label_frontend', 'attribute_code' => 'fpt_code', ], - 'expectedFptLabel' => 'fpt_label_frontend' - ] + 'websitePassed' => 1, + 'expectedFptLabel' => 'fpt_label_frontend', + ], + 'website_not_passed' => [ + 'weeeTaxCalculationsByEntity' => [ + 'weee_value' => 1, + 'label_value' => '', + 'frontend_label' => 'fpt_label_frontend', + 'attribute_code' => 'fpt_code', + ], + 'websitePassed' => null, + 'expectedFptLabel' => 'fpt_label_frontend', + ], ]; } diff --git a/app/code/Magento/Weee/Test/Unit/Ui/DataProvider/Product/Listing/Collector/WeeeTest.php b/app/code/Magento/Weee/Test/Unit/Ui/DataProvider/Product/Listing/Collector/WeeeTest.php index 6be1713c99004..266737089cd21 100644 --- a/app/code/Magento/Weee/Test/Unit/Ui/DataProvider/Product/Listing/Collector/WeeeTest.php +++ b/app/code/Magento/Weee/Test/Unit/Ui/DataProvider/Product/Listing/Collector/WeeeTest.php @@ -41,6 +41,9 @@ class WeeeTest extends \PHPUnit\Framework\TestCase /** @var FormattedPriceInfoBuilder|\PHPUnit_Framework_MockObject_MockObject */ private $formattedPriceInfoBuilder; + /** + * @return void + */ protected function setUp() { $this->weeeHelperMock = $this->getMockBuilder(\Magento\Weee\Helper\Data::class) @@ -50,14 +53,16 @@ protected function setUp() ->getMockForAbstractClass(); $this->weeeAdjustmentAttributeFactory = $this->getMockBuilder(WeeeAdjustmentAttributeInterfaceFactory::class) + ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); $this->extensionAttributes = $this->getMockBuilder(PriceInfoExtensionInterface::class) ->setMethods(['setWeeeAttributes', 'setWeeeAdjustment']) - ->getMock(); + ->getMockForAbstractClass(); $this->priceInfoExtensionFactory = $this->getMockBuilder(PriceInfoExtensionInterfaceFactory::class) + ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); @@ -74,6 +79,9 @@ protected function setUp() ); } + /** + * @return void + */ public function testCollect() { $productMock = $this->getMockBuilder(Product::class) diff --git a/app/code/Magento/Weee/etc/db_schema_whitelist.json b/app/code/Magento/Weee/etc/db_schema_whitelist.json index cf7c72a87a7fa..eb18574877cfe 100644 --- a/app/code/Magento/Weee/etc/db_schema_whitelist.json +++ b/app/code/Magento/Weee/etc/db_schema_whitelist.json @@ -1,79 +1,79 @@ { - "weee_tax": { - "column": { - "value_id": true, - "website_id": true, - "entity_id": true, - "country": true, - "value": true, - "state": true, - "attribute_id": true + "weee_tax": { + "column": { + "value_id": true, + "website_id": true, + "entity_id": true, + "country": true, + "value": true, + "state": true, + "attribute_id": true + }, + "index": { + "WEEE_TAX_WEBSITE_ID": true, + "WEEE_TAX_ENTITY_ID": true, + "WEEE_TAX_COUNTRY": true, + "WEEE_TAX_ATTRIBUTE_ID": true + }, + "constraint": { + "PRIMARY": true, + "WEEE_TAX_COUNTRY_DIRECTORY_COUNTRY_COUNTRY_ID": true, + "WEEE_TAX_ENTITY_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "WEEE_TAX_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, + "WEEE_TAX_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, + "WEEE_TAX_ENTITY_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true + } }, - "index": { - "WEEE_TAX_WEBSITE_ID": true, - "WEEE_TAX_ENTITY_ID": true, - "WEEE_TAX_COUNTRY": true, - "WEEE_TAX_ATTRIBUTE_ID": true + "quote_item": { + "column": { + "weee_tax_applied": true, + "weee_tax_applied_amount": true, + "weee_tax_applied_row_amount": true, + "weee_tax_disposition": true, + "weee_tax_row_disposition": true, + "base_weee_tax_applied_amount": true, + "base_weee_tax_applied_row_amnt": true, + "base_weee_tax_disposition": true, + "base_weee_tax_row_disposition": true + } }, - "constraint": { - "PRIMARY": true, - "WEEE_TAX_COUNTRY_DIRECTORY_COUNTRY_COUNTRY_ID": true, - "WEEE_TAX_ENTITY_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "WEEE_TAX_WEBSITE_ID_STORE_WEBSITE_WEBSITE_ID": true, - "WEEE_TAX_ATTRIBUTE_ID_EAV_ATTRIBUTE_ATTRIBUTE_ID": true, - "WEEE_TAX_ENTITY_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true - } - }, - "quote_item": { - "column": { - "weee_tax_applied": true, - "weee_tax_applied_amount": true, - "weee_tax_applied_row_amount": true, - "weee_tax_disposition": true, - "weee_tax_row_disposition": true, - "base_weee_tax_applied_amount": true, - "base_weee_tax_applied_row_amnt": true, - "base_weee_tax_disposition": true, - "base_weee_tax_row_disposition": true - } - }, - "sales_order_item": { - "column": { - "weee_tax_applied": true, - "weee_tax_applied_amount": true, - "weee_tax_applied_row_amount": true, - "weee_tax_disposition": true, - "weee_tax_row_disposition": true, - "base_weee_tax_applied_amount": true, - "base_weee_tax_applied_row_amnt": true, - "base_weee_tax_disposition": true, - "base_weee_tax_row_disposition": true - } - }, - "sales_invoice_item": { - "column": { - "weee_tax_applied": true, - "weee_tax_applied_amount": true, - "weee_tax_applied_row_amount": true, - "weee_tax_disposition": true, - "weee_tax_row_disposition": true, - "base_weee_tax_applied_amount": true, - "base_weee_tax_applied_row_amnt": true, - "base_weee_tax_disposition": true, - "base_weee_tax_row_disposition": true - } - }, - "sales_creditmemo_item": { - "column": { - "weee_tax_applied": true, - "weee_tax_applied_amount": true, - "weee_tax_applied_row_amount": true, - "weee_tax_disposition": true, - "weee_tax_row_disposition": true, - "base_weee_tax_applied_amount": true, - "base_weee_tax_applied_row_amnt": true, - "base_weee_tax_disposition": true, - "base_weee_tax_row_disposition": true + "sales_order_item": { + "column": { + "weee_tax_applied": true, + "weee_tax_applied_amount": true, + "weee_tax_applied_row_amount": true, + "weee_tax_disposition": true, + "weee_tax_row_disposition": true, + "base_weee_tax_applied_amount": true, + "base_weee_tax_applied_row_amnt": true, + "base_weee_tax_disposition": true, + "base_weee_tax_row_disposition": true + } + }, + "sales_invoice_item": { + "column": { + "weee_tax_applied": true, + "weee_tax_applied_amount": true, + "weee_tax_applied_row_amount": true, + "weee_tax_disposition": true, + "weee_tax_row_disposition": true, + "base_weee_tax_applied_amount": true, + "base_weee_tax_applied_row_amnt": true, + "base_weee_tax_disposition": true, + "base_weee_tax_row_disposition": true + } + }, + "sales_creditmemo_item": { + "column": { + "weee_tax_applied": true, + "weee_tax_applied_amount": true, + "weee_tax_applied_row_amount": true, + "weee_tax_disposition": true, + "weee_tax_row_disposition": true, + "base_weee_tax_applied_amount": true, + "base_weee_tax_applied_row_amnt": true, + "base_weee_tax_disposition": true, + "base_weee_tax_row_disposition": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/WeeeGraphQl/Test/Mftf/composer.json b/app/code/Magento/WeeeGraphQl/Test/Mftf/composer.json deleted file mode 100644 index 09b1dc9afe551..0000000000000 --- a/app/code/Magento/WeeeGraphQl/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-weee-graph-ql", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-weee": "100.0.0-dev", - "magento/functional-test-module-catalog-graph-ql": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php index 49345f29afd53..c48bf9e7e4c7a 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php @@ -27,7 +27,7 @@ class Layout extends Template implements RendererInterface /** * @var string */ - protected $_template = 'instance/edit/layout.phtml'; + protected $_template = 'Magento_Widget::instance/edit/layout.phtml'; /** * @var \Magento\Catalog\Model\Product\Type diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php index 98275c3b906db..b5d31dc10be34 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/Save.php @@ -49,7 +49,5 @@ public function execute() $this->_redirect('adminhtml/*/edit', ['_current' => true]); return; } - $this->_redirect('adminhtml/*/'); - return; } } diff --git a/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml new file mode 100644 index 0000000000000..2c4e2e70fec71 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + + <!-- This test exists to serve as a base for extension for other tests --> + <test name="NewProductsListWidgetTest"> + <annotations> + <group value="WYSIWYGDisabled"/> + <features value="Widget"/> + <stories value="New products list widget"/> + <title value="Admin should be able to set products as new so that they show up in the Catalog New Products List Widget"/> + <description value="Admin should be able to set products as new so that they show up in the Catalog New Products List Widget"/> + <severity value="MAJOR"/> + <group value="Widget"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + </before> + + <after> + <amOnPage url="admin/admin/auth/logout/" stepKey="logout"/> + </after> + + <!-- Create a CMS page containing the New Products widget --> + + <amOnPage url="{{CmsPagesPage.url}}" stepKey="amOnCmsList"/> + <waitForPageLoad stepKey="waitForCmsList"/> + <click selector="{{CmsPagesPageActionsSection.addNewPageButton}}" stepKey="clickAddNewPageButton"/> + <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_newDefaultCmsPage.title}}" stepKey="fillPageTitle"/> + <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="expandContentSection"/> + <waitForPageLoad stepKey="waitForContentSection"/> + <click selector="{{CmsWYSIWYGSection.InsertWidgetBtn}}" stepKey="clickInsertWidgetButton"/> + <waitForPageLoad stepKey="waitForSlideOut"/> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Catalog New Products List" stepKey="selectWidgetType"/> + <waitForPageLoad stepKey="waitForWidgetOptions"/> + <selectOption selector="{{WidgetSection.DisplayType}}" userInput="New products" stepKey="selectDisplayType"/> + <fillField selector="{{WidgetSection.NoOfProductToDisplay}}" userInput="100" stepKey="fillNoOfProductToDisplay"/> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidget"/> + <click selector="{{CmsNewPagePageSeoSection.header}}" stepKey="expandSeoSection"/> + <fillField selector="{{CmsNewPagePageSeoSection.urlKey}}" userInput="{{_newDefaultCmsPage.identifier}}" stepKey="fillPageUrlKey"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickSaveCmsPage"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Widget/Test/Mftf/composer.json b/app/code/Magento/Widget/Test/Mftf/composer.json deleted file mode 100644 index 693fa651f5974..0000000000000 --- a/app/code/Magento/Widget/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-widget", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-widget-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php index 0e1f722cd366a..b85a458ed4121 100644 --- a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php +++ b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php @@ -168,7 +168,7 @@ public function testGetWidgetDeclaration() 'show_pager' => '1', 'products_per_page' => '5', 'products_count' => '10', - 'template' => 'product/widget/content/grid.phtml', + 'template' => 'Magento_CatalogWidget::product/widget/content/grid.phtml', 'conditions' => $conditions ]; @@ -181,7 +181,10 @@ public function testGetWidgetDeclaration() ['1', false, '1'], ['5', false, '5'], ['10', false, '10'], - ['product/widget/content/grid.phtml', false, 'product/widget/content/grid.phtml'], + ['Magento_CatalogWidget::product/widget/content/grid.phtml', + false, + 'Magento_CatalogWidget::product/widget/content/grid.phtml' + ], ['encoded-conditions-string', false, 'encoded-conditions-string'], ]); @@ -226,7 +229,7 @@ public function testGetWidgetDeclarationWithZeroValueParam() 'show_pager' => '1', 'products_per_page' => '5', 'products_count' => '0', - 'template' => 'product/widget/content/grid.phtml', + 'template' => 'Magento_CatalogWidget::product/widget/content/grid.phtml', 'conditions' => $conditions ]; diff --git a/app/code/Magento/Widget/etc/db_schema_whitelist.json b/app/code/Magento/Widget/etc/db_schema_whitelist.json index 431ade7f52f04..a42646a355045 100644 --- a/app/code/Magento/Widget/etc/db_schema_whitelist.json +++ b/app/code/Magento/Widget/etc/db_schema_whitelist.json @@ -1,98 +1,98 @@ { - "widget": { - "column": { - "widget_id": true, - "widget_code": true, - "widget_type": true, - "parameters": true + "widget": { + "column": { + "widget_id": true, + "widget_code": true, + "widget_type": true, + "parameters": true + }, + "index": { + "WIDGET_WIDGET_CODE": true + }, + "constraint": { + "PRIMARY": true + } }, - "index": { - "WIDGET_WIDGET_CODE": true + "widget_instance": { + "column": { + "instance_id": true, + "instance_type": true, + "theme_id": true, + "title": true, + "store_ids": true, + "widget_parameters": true, + "sort_order": true + }, + "constraint": { + "PRIMARY": true, + "WIDGET_INSTANCE_THEME_ID_THEME_THEME_ID": true + } }, - "constraint": { - "PRIMARY": true - } - }, - "widget_instance": { - "column": { - "instance_id": true, - "instance_type": true, - "theme_id": true, - "title": true, - "store_ids": true, - "widget_parameters": true, - "sort_order": true - }, - "constraint": { - "PRIMARY": true, - "WIDGET_INSTANCE_THEME_ID_THEME_THEME_ID": true - } - }, - "widget_instance_page": { - "column": { - "page_id": true, - "instance_id": true, - "page_group": true, - "layout_handle": true, - "block_reference": true, - "page_for": true, - "entities": true, - "page_template": true - }, - "index": { - "WIDGET_INSTANCE_PAGE_INSTANCE_ID": true - }, - "constraint": { - "PRIMARY": true, - "WIDGET_INSTANCE_PAGE_INSTANCE_ID_WIDGET_INSTANCE_INSTANCE_ID": true - } - }, - "widget_instance_page_layout": { - "column": { - "page_id": true, - "layout_update_id": true + "widget_instance_page": { + "column": { + "page_id": true, + "instance_id": true, + "page_group": true, + "layout_handle": true, + "block_reference": true, + "page_for": true, + "entities": true, + "page_template": true + }, + "index": { + "WIDGET_INSTANCE_PAGE_INSTANCE_ID": true + }, + "constraint": { + "PRIMARY": true, + "WIDGET_INSTANCE_PAGE_INSTANCE_ID_WIDGET_INSTANCE_INSTANCE_ID": true + } }, - "index": { - "WIDGET_INSTANCE_PAGE_LAYOUT_PAGE_ID": true - }, - "constraint": { - "WIDGET_INSTANCE_PAGE_LAYOUT_PAGE_ID_WIDGET_INSTANCE_PAGE_PAGE_ID": true, - "WIDGET_INSTANCE_PAGE_LYT_LYT_UPDATE_ID_LYT_UPDATE_LYT_UPDATE_ID": true, - "WIDGET_INSTANCE_PAGE_LAYOUT_LAYOUT_UPDATE_ID_PAGE_ID": true - } - }, - "layout_update": { - "column": { - "layout_update_id": true, - "handle": true, - "xml": true, - "sort_order": true, - "updated_at": true - }, - "index": { - "LAYOUT_UPDATE_HANDLE": true - }, - "constraint": { - "PRIMARY": true - } - }, - "layout_link": { - "column": { - "layout_link_id": true, - "store_id": true, - "theme_id": true, - "layout_update_id": true, - "is_temporary": true + "widget_instance_page_layout": { + "column": { + "page_id": true, + "layout_update_id": true + }, + "index": { + "WIDGET_INSTANCE_PAGE_LAYOUT_PAGE_ID": true + }, + "constraint": { + "WIDGET_INSTANCE_PAGE_LAYOUT_PAGE_ID_WIDGET_INSTANCE_PAGE_PAGE_ID": true, + "WIDGET_INSTANCE_PAGE_LYT_LYT_UPDATE_ID_LYT_UPDATE_LYT_UPDATE_ID": true, + "WIDGET_INSTANCE_PAGE_LAYOUT_LAYOUT_UPDATE_ID_PAGE_ID": true + } }, - "index": { - "LAYOUT_LINK_LAYOUT_UPDATE_ID": true, - "LAYOUT_LINK_STORE_ID_THEME_ID_LAYOUT_UPDATE_ID_IS_TEMPORARY": true + "layout_update": { + "column": { + "layout_update_id": true, + "handle": true, + "xml": true, + "sort_order": true, + "updated_at": true + }, + "index": { + "LAYOUT_UPDATE_HANDLE": true + }, + "constraint": { + "PRIMARY": true + } }, - "constraint": { - "PRIMARY": true, - "LAYOUT_LINK_LAYOUT_UPDATE_ID_LAYOUT_UPDATE_LAYOUT_UPDATE_ID": true, - "LAYOUT_LINK_STORE_ID_STORE_STORE_ID": true, - "LAYOUT_LINK_THEME_ID_THEME_THEME_ID": true + "layout_link": { + "column": { + "layout_link_id": true, + "store_id": true, + "theme_id": true, + "layout_update_id": true, + "is_temporary": true + }, + "index": { + "LAYOUT_LINK_LAYOUT_UPDATE_ID": true, + "LAYOUT_LINK_STORE_ID_THEME_ID_LAYOUT_UPDATE_ID_IS_TEMPORARY": true + }, + "constraint": { + "PRIMARY": true, + "LAYOUT_LINK_LAYOUT_UPDATE_ID_LAYOUT_UPDATE_LAYOUT_UPDATE_ID": true, + "LAYOUT_LINK_STORE_ID_STORE_STORE_ID": true, + "LAYOUT_LINK_THEME_ID_THEME_THEME_ID": true + } } - } } \ No newline at end of file diff --git a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml index a0a0fc040a262..3441cf6b5d52a 100644 --- a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml +++ b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml @@ -304,11 +304,6 @@ var WidgetInstance = { }, displayPageGroup : function(container, additional) { container = $(container); - if (!container) { -// if (activePageGroupId = this.activePageGroups.get(container.up('div.page_group_container').id)) { -// this.hideBlockContainer(activePageGroupId); -// } - } if (!additional) { additional = {}; } diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Image.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Image.php index 02a897d44b3c6..5595d189b15eb 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Image.php +++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Image.php @@ -3,18 +3,59 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * Wishlist block customer item cart column - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\View\ConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; + /** + * Wishlist block customer item cart column + * * @api * @since 100.0.2 */ class Image extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column { + /** @var ItemResolverInterface */ + private $itemResolver; + + /** + * @param \Magento\Catalog\Block\Product\Context $context + * @param \Magento\Framework\App\Http\Context $httpContext + * @param array $data + * @param ConfigInterface|null $config + * @param UrlBuilder|null $urlBuilder + * @param ItemResolverInterface|null $itemResolver + */ + public function __construct( + \Magento\Catalog\Block\Product\Context $context, + \Magento\Framework\App\Http\Context $httpContext, + array $data = [], + ConfigInterface $config = null, + UrlBuilder $urlBuilder = null, + ItemResolverInterface $itemResolver = null + ) { + $this->itemResolver = $itemResolver ?: ObjectManager::getInstance()->get(ItemResolverInterface::class); + parent::__construct( + $context, + $httpContext, + $data, + $config, + $urlBuilder + ); + } + + /** + * Identify the product from which thumbnail should be taken. + * + * @return \Magento\Catalog\Model\Product + */ + public function getProductForThumbnail(\Magento\Wishlist\Model\Item $item) : \Magento\Catalog\Model\Product + { + return $this->itemResolver->getFinalProduct($item); + } } diff --git a/app/code/Magento/Wishlist/Block/Rss/EmailLink.php b/app/code/Magento/Wishlist/Block/Rss/EmailLink.php index 4a5f116cd8293..907dfd90e752e 100644 --- a/app/code/Magento/Wishlist/Block/Rss/EmailLink.php +++ b/app/code/Magento/Wishlist/Block/Rss/EmailLink.php @@ -21,7 +21,7 @@ class EmailLink extends Link /** * @var string */ - protected $_template = 'rss/email.phtml'; + protected $_template = 'Magento_Wishlist::rss/email.phtml'; /** * @return array diff --git a/app/code/Magento/Wishlist/Block/Share/Email/Items.php b/app/code/Magento/Wishlist/Block/Share/Email/Items.php index bc84f6a43df3c..d4e6587fd6519 100644 --- a/app/code/Magento/Wishlist/Block/Share/Email/Items.php +++ b/app/code/Magento/Wishlist/Block/Share/Email/Items.php @@ -20,7 +20,7 @@ class Items extends \Magento\Wishlist\Block\AbstractBlock /** * @var string */ - protected $_template = 'email/items.phtml'; + protected $_template = 'Magento_Wishlist::email/items.phtml'; /** * Retrieve Product View URL diff --git a/app/code/Magento/Wishlist/Controller/Index/Remove.php b/app/code/Magento/Wishlist/Controller/Index/Remove.php index ec45201e390f1..84c59b5be3d1e 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Remove.php +++ b/app/code/Magento/Wishlist/Controller/Index/Remove.php @@ -10,6 +10,8 @@ use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Controller\ResultFactory; use Magento\Wishlist\Controller\WishlistProviderInterface; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\Product\AttributeValueProvider; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -26,18 +28,27 @@ class Remove extends \Magento\Wishlist\Controller\AbstractIndex */ protected $formKeyValidator; + /** + * @var AttributeValueProvider + */ + private $attributeValueProvider; + /** * @param Action\Context $context * @param WishlistProviderInterface $wishlistProvider * @param Validator $formKeyValidator + * @param AttributeValueProvider|null $attributeValueProvider */ public function __construct( Action\Context $context, WishlistProviderInterface $wishlistProvider, - Validator $formKeyValidator + Validator $formKeyValidator, + AttributeValueProvider $attributeValueProvider = null ) { $this->wishlistProvider = $wishlistProvider; $this->formKeyValidator = $formKeyValidator; + $this->attributeValueProvider = $attributeValueProvider + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(AttributeValueProvider::class); parent::__construct($context); } @@ -56,7 +67,8 @@ public function execute() } $id = (int)$this->getRequest()->getParam('item'); - $item = $this->_objectManager->create(\Magento\Wishlist\Model\Item::class)->load($id); + /** @var Item $item */ + $item = $this->_objectManager->create(Item::class)->load($id); if (!$item->getId()) { throw new NotFoundException(__('Page not found.')); } @@ -67,6 +79,14 @@ public function execute() try { $item->delete(); $wishlist->save(); + $productName = $this->attributeValueProvider + ->getRawAttributeValue($item->getProductId(), 'name'); + $this->messageManager->addComplexSuccessMessage( + 'removeWishlistItemSuccessMessage', + [ + 'product_name' => $productName, + ] + ); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError( __('We can\'t delete the item from Wish List right now because of an error: %1.', $e->getMessage()) @@ -76,13 +96,8 @@ public function execute() } $this->_objectManager->get(\Magento\Wishlist\Helper\Data::class)->calculate(); - $request = $this->getRequest(); - $refererUrl = (string)$request->getServer('HTTP_REFERER'); - $url = (string)$request->getParam(\Magento\Framework\App\Response\RedirectInterface::PARAM_NAME_REFERER_URL); - if ($url) { - $refererUrl = $url; - } - if ($request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED) && $refererUrl) { + $refererUrl = $this->_redirect->getRefererUrl(); + if ($refererUrl) { $redirectUrl = $refererUrl; } else { $redirectUrl = $this->_redirect->getRedirectUrl($this->_url->getUrl('*/*')); diff --git a/app/code/Magento/Wishlist/CustomerData/Wishlist.php b/app/code/Magento/Wishlist/CustomerData/Wishlist.php index eeae07861e22d..ae54289d4b1c9 100644 --- a/app/code/Magento/Wishlist/CustomerData/Wishlist.php +++ b/app/code/Magento/Wishlist/CustomerData/Wishlist.php @@ -7,6 +7,7 @@ use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException; use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Framework\App\ObjectManager; /** * Wishlist section @@ -38,22 +39,32 @@ class Wishlist implements SectionSourceInterface */ protected $block; + /** + * @var \Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface + */ + private $itemResolver; + /** * @param \Magento\Wishlist\Helper\Data $wishlistHelper * @param \Magento\Wishlist\Block\Customer\Sidebar $block * @param \Magento\Catalog\Helper\ImageFactory $imageHelperFactory * @param \Magento\Framework\App\ViewInterface $view + * @param \Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface|null $itemResolver */ public function __construct( \Magento\Wishlist\Helper\Data $wishlistHelper, \Magento\Wishlist\Block\Customer\Sidebar $block, \Magento\Catalog\Helper\ImageFactory $imageHelperFactory, - \Magento\Framework\App\ViewInterface $view + \Magento\Framework\App\ViewInterface $view, + \Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface $itemResolver = null ) { $this->wishlistHelper = $wishlistHelper; $this->imageHelperFactory = $imageHelperFactory; $this->block = $block; $this->view = $view; + $this->itemResolver = $itemResolver ?: ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface::class + ); } /** @@ -122,7 +133,7 @@ protected function getItemData(\Magento\Wishlist\Model\Item $wishlistItem) { $product = $wishlistItem->getProduct(); return [ - 'image' => $this->getImageData($product), + 'image' => $this->getImageData($this->itemResolver->getFinalProduct($wishlistItem)), 'product_sku' => $product->getSku(), 'product_id' => $product->getId(), 'product_url' => $this->wishlistHelper->getProductUrl($wishlistItem), @@ -135,8 +146,8 @@ protected function getItemData(\Magento\Wishlist\Model\Item $wishlistItem) ), 'product_is_saleable_and_visible' => $product->isSaleable() && $product->isVisibleInSiteVisibility(), 'product_has_required_options' => $product->getTypeInstance()->hasRequiredOptions($product), - 'add_to_cart_params' => $this->wishlistHelper->getAddToCartParams($wishlistItem, true), - 'delete_item_params' => $this->wishlistHelper->getRemoveParams($wishlistItem, true), + 'add_to_cart_params' => $this->wishlistHelper->getAddToCartParams($wishlistItem), + 'delete_item_params' => $this->wishlistHelper->getRemoveParams($wishlistItem), ]; } @@ -149,14 +160,6 @@ protected function getItemData(\Magento\Wishlist\Model\Item $wishlistItem) */ protected function getImageData($product) { - /*Set variant product if it is configurable product. - It will show variant product image in sidebar instead of configurable product image.*/ - $simpleOption = $product->getCustomOption('simple_product'); - if ($simpleOption !== null) { - $optionProduct = $simpleOption->getProduct(); - $product = $optionProduct; - } - /** @var \Magento\Catalog\Helper\Image $helper */ $helper = $this->imageHelperFactory->create() ->init($product, 'wishlist_sidebar_block'); diff --git a/app/code/Magento/Wishlist/Helper/Data.php b/app/code/Magento/Wishlist/Helper/Data.php index 968a7f1db8c39..f4c1aa9662bda 100644 --- a/app/code/Magento/Wishlist/Helper/Data.php +++ b/app/code/Magento/Wishlist/Helper/Data.php @@ -284,9 +284,12 @@ public function getRemoveParams($item, $addReferer = false) { $url = $this->_getUrl('wishlist/index/remove'); $params = ['item' => $item->getWishlistItemId()]; + $params[ActionInterface::PARAM_NAME_URL_ENCODED] = ''; + if ($addReferer) { $params = $this->addRefererToParams($params); } + return $this->_postDataHelper->getPostData($url, $params); } @@ -395,9 +398,12 @@ public function getAddToCartUrl($item) public function getAddToCartParams($item, $addReferer = false) { $params = $this->_getCartUrlParameters($item); + $params[ActionInterface::PARAM_NAME_URL_ENCODED] = ''; + if ($addReferer) { $params = $this->addRefererToParams($params); } + return $this->_postDataHelper->getPostData( $this->_getUrlStore($item)->getUrl('wishlist/index/cart'), $params diff --git a/app/code/Magento/Wishlist/Model/Item.php b/app/code/Magento/Wishlist/Model/Item.php index b0e7c78cae5f4..41e83c7179e10 100644 --- a/app/code/Magento/Wishlist/Model/Item.php +++ b/app/code/Magento/Wishlist/Model/Item.php @@ -473,7 +473,7 @@ public function getProductUrl() public function getBuyRequest() { $option = $this->getOptionByCode('info_buyRequest'); - $initialData = $option ? $this->serializer->unserialize($option->getValue()) : null; + $initialData = $option ? $this->serializer->unserialize($option->getValue()) : []; if ($initialData instanceof \Magento\Framework\DataObject) { $initialData = $initialData->getData(); diff --git a/app/code/Magento/Wishlist/Model/Product/AttributeValueProvider.php b/app/code/Magento/Wishlist/Model/Product/AttributeValueProvider.php new file mode 100644 index 0000000000000..e17cd30184504 --- /dev/null +++ b/app/code/Magento/Wishlist/Model/Product/AttributeValueProvider.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Model\Product; + +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; + +/** + * Provides existing attribute value for a product entity. + */ +class AttributeValueProvider +{ + /** + * @var ProductCollectionFactory + */ + private $productCollectionFactory; + + /** + * @param ProductCollectionFactory $productCollectionFactory + */ + public function __construct( + ProductCollectionFactory $productCollectionFactory + ) { + $this->productCollectionFactory = $productCollectionFactory; + } + + /** + * Provides existing raw attribute value by the attribute code of the product entity. + * + * @param int $productId + * @param string $attributeCode + * @param int|null $storeId + * @return null|string + */ + public function getRawAttributeValue(int $productId, string $attributeCode, int $storeId = null):? string + { + $collection = $this->productCollectionFactory->create(); + $collection->addIdFilter($productId) + ->addStoreFilter($storeId) + ->addAttributeToSelect($attributeCode); + + if ($collection->isEnabledFlat()) { + $data = $collection->getConnection()->fetchRow($collection->getSelect()); + $attributeValue = $data[$attributeCode] ?? null; + } else { + $attributeValue = $collection->getFirstItem()->getData($attributeCode); + } + + return $attributeValue; + } +} diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml index dd816bb665782..fff1f44c17c45 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Add Product to wishlist from the category page and check message --> <actionGroup name="StorefrontCustomerAddCategoryProductToWishlistActionGroup"> <arguments> @@ -53,4 +53,24 @@ <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductAddToCartByName(productVar.name)}}" stepKey="AssertWishlistSidebarAddToCart" /> <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductImageByName(productVar.name)}}" stepKey="AssertWishlistSidebarProductImage" /> </actionGroup> -</actionGroups> \ No newline at end of file + + <!--Remove a product from the wishlist using the sidebar --> + <actionGroup name="StorefrontCustomerRemoveProductFromWishlistUsingSidebar"> + <arguments> + <argument name="product"/> + </arguments> + <click selector="{{StorefrontCustomerWishlistSidebarSection.ProductRemoveByName(product.name)}}" stepKey="RemoveProductFromWishlistUsingSidebarClickRemoveItemFromWishlist"/> + <waitForElement selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="RemoveProductFromWishlistUsingSidebarWaitForSuccessMessage"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="{{product.name}} has been removed from your Wish List." stepKey="RemoveProductFromWishlistUsingSidebarSeeProductNameRemovedFromWishlist"/> + </actionGroup> + + <!--Add a product to the cart from the wishlist using the sidebar --> + <actionGroup name="StorefrontCustomerAddProductToCartFromWishlistUsingSidebar"> + <arguments> + <argument name="product"/> + </arguments> + <click selector="{{StorefrontCustomerWishlistSidebarSection.ProductAddToCartByName(product.name)}}" stepKey="AddProductToCartFromWishlistUsingSidebarClickAddToCartFromWishlist"/> + <waitForElement selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="AddProductToCartFromWishlistUsingSidebarWaitForSuccessMessage"/> + <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added {{product.name}} to your shopping cart." stepKey="AddProductToCartFromWishlistUsingSidebarSeeProductNameAddedToCartFromWishlist"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml index 93fb9a5689e17..811871bf685ae 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml @@ -6,7 +6,7 @@ */ --> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Wishlist" type="wishlist"> <data key="id">null</data> <var key="product" entityType="product" entityKey="id"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Metadata/wishlist-meta.xml b/app/code/Magento/Wishlist/Test/Mftf/Metadata/wishlist-meta.xml index 37f2bbe6a29d5..423367c9a3b9d 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Metadata/wishlist-meta.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Metadata/wishlist-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateWishlist" dataType="wishlist" type="create" auth="customerFormKey" url="/wishlist/index/add/" method="POST" successRegex="" returnRegex="~\/wishlist_id\/(\d*?)\/~" > <contentType>application/x-www-form-urlencoded</contentType> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistPage.xml b/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistPage.xml index cf2db7efab6c6..986d1e59ad066 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistPage.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerWishlistPage" url="/wishlist/" area="storefront" module="Magento_Wishlist"> <section name="StorefrontCustomerWishlistSection" /> </page> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCategoryProductSection.xml index 20d72a0704699..07f8b91661d2e 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCategoryProductSection.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCategoryProductSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryProductSection"> <element name="ProductAddToWishlistByNumber" type="text" selector="//main//li[{{var1}}]//a[contains(@class, 'towishlist')]" parameterized="true"/> <element name="ProductAddToWishlistByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//a[contains(@class, 'towishlist')]" parameterized="true"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml index bd9fadb630c95..4bc4b3f1b9274 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml @@ -7,12 +7,13 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerWishlistProductSection"> <element name="ProductTitleByName" type="button" selector="//main//li//a[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="ProductPriceByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//span[@class='price']" parameterized="true"/> <element name="ProductImageByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//img[@class='product-image-photo']" parameterized="true"/> <element name="ProductInfoByName" type="text" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//div[@class='product-item-info']" parameterized="true"/> <element name="ProductAddToCartByName" type="button" selector="//main//li[.//a[contains(text(), '{{var1}}')]]//button[contains(@class, 'action tocart primary')]" parameterized="true"/> + <element name="ProductImageByImageName" type="text" selector="//main//li//a//img[contains(@src, '{{var1}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSection.xml index 747ad958adbe0..c208bfc41dcdc 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSection.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerWishlistSection"> <element name="pageTitle" type="text" selector="h1.page-title"/> <element name="successMsg" type="text" selector="div.message-success.success.message"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSidebarSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSidebarSection.xml index d3e89d419310c..ba226837c5fe7 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSidebarSection.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistSidebarSection.xml @@ -7,11 +7,13 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerWishlistSidebarSection"> <element name="ProductTitleByName" type="button" selector="//main//ol[@id='wishlist-sidebar']//a[@class='product-item-link']/span[text()='{{var1}}']" parameterized="true"/> <element name="ProductPriceByName" type="text" selector="//main//ol[@id='wishlist-sidebar']//a[@class='product-item-link']/span[text()='{{var1}}']//ancestor::ol//span[@class='price']" parameterized="true"/> <element name="ProductImageByName" type="text" selector="//main//ol[@id='wishlist-sidebar']//a[@class='product-item-link']/span[text()='{{var1}}']//ancestor::ol//img[@class='product-image-photo']" parameterized="true"/> <element name="ProductAddToCartByName" type="button" selector="//main//ol[@id='wishlist-sidebar']//a[@class='product-item-link']/span[text()='{{var1}}']//ancestor::ol//button[contains(@class, 'action tocart primary')]" parameterized="true"/> + <element name="ProductImageByImageName" type="text" selector="//main//ol[@id='wishlist-sidebar']//a//img[contains(@src, '{{var1}}')]" parameterized="true"/> + <element name="ProductRemoveByName" type="button" selector="//main//ol[@id='wishlist-sidebar']//a[@class='product-item-link']/span[text()='{{var1}}']//ancestor::ol//a[contains(@class, 'action delete')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index ea2dfcbedaa27..e77c489074069 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductInfoMainSection"> <element name="productAddToWishlist" type="button" selector="a.action.towishlist"/> </section> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml new file mode 100644 index 0000000000000..42d4203999a44 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ConfigurableProductChildImageShouldBeShownOnWishListTest"> + <annotations> + <features value="Wishlist"/> + <stories value="MAGETWO-8709"/> + <group value="wishlist"/> + <title value="When user add Configurable child product to WIshlist then child product image should be shown in Wishlist"/> + <description value="When user add Configurable child product to WIshlist then child product image should be shown in Wishlist"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-93097"/> + </annotations> + <before> + <magentoCLI command="config:set checkout/cart/configurable_product_image 0" stepKey="setProductImageSettingUnderCofigurationSalesCheckout"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + + <actionGroup ref="createConfigurableProduct" stepKey="createProduct"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetFiltersIfPresent"/> + <actionGroup ref="searchProductGridByKeyword" stepKey="searchProductGrid"> + <argument name="keyword" value="_defaultProduct.name"/> + </actionGroup> + <click selector="{{AdminProductGridSection.selectRowBasedOnName(_defaultProduct.name)}}" stepKey="selectProductToAddImage"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad"/> + <actionGroup ref="addProductImage" stepKey="addImageForParentProduct"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex1"/> + <waitForPageLoad stepKey="waitForProductIndexPageLoad1"/> + <click selector="{{AdminProductGridSection.selectRowBasedOnName(colorProductAttribute1.name)}}" stepKey="selectProductToAddImage1"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad1"/> + <actionGroup ref="addProductImage" stepKey="addImageForChildProduct"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> + + <!--Sign in as customer --> + <amOnPage url="{{StorefrontCustomerSignInPage.url}}" stepKey="amOnSignInPage"/> + <fillField userInput="$$customer.email$$" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> + <fillField userInput="$$customer.password$$" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" stepKey="waitForButton"/> + <click selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" stepKey="clickSignInAccountButton"/> + <see userInput="$$customer.firstname$$" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" stepKey="seeFirstName"/> + <see userInput="$$customer.lastname$$" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" stepKey="seeLastName"/> + <see userInput="$$customer.email$$" selector="{{StorefrontCustomerDashboardAccountInformationSection.ContactInformation}}" stepKey="seeEmail"/> + <waitForPageLoad stepKey="waitForLogin"/> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="amOnConfigurableProductPage"/> + <waitForPageLoad stepKey="wait3"/> + <see userInput="{{_defaultProduct.name}}" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="seeProductName"/> + <selectOption userInput="{{colorProductAttribute1.name}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption1"/> + <waitForElementVisible selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="waitForAddToCartVisible"/> + <click selector="{{StorefrontProductPageSection.addToWishlist}}" stepKey="addFirstProductToWishlist"/> + <waitForElement selector="{{StorefrontCustomerWishlistSection.successMsg}}" time="30" stepKey="addProductToWishlistWaitForSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <seeElement selector="{{StorefrontCustomerWishlistProductSection.ProductImageByImageName(TestImageNew.filename)}}" stepKey="AssertWishlistProductImage" /> + <seeElement selector="{{StorefrontCustomerWishlistSidebarSection.ProductImageByImageName(TestImageNew.filename)}}" stepKey="AssertWishlistSidebarProductImage" /> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 194737788eb7c..7eb42d1fbfed9 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <!-- Step 5: Add products to wishlist --> <comment userInput="Start of adding products to wishlist" stepKey="startOfAddingProductsToWishlist" after="endOfComparingProducts" /> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml index 0a4d241b0e4ab..3d2d6d8781be0 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml @@ -6,13 +6,15 @@ */ --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontAddMultipleStoreProductsToWishlistTest"> <annotations> <features value="Wishlist"/> + <stories value="Adding to wishlist"/> <title value="Customer should be able to add products to wishlist from different stores"/> <description value="All products added to wishlist should be visible on any store. Even if product visibility was set to 'Not Visible Individually' for this store"/> <group value="wishlist"/> + <severity value="AVERAGE"/> </annotations> <before> <createData entity="customStoreGroup" stepKey="storeGroup"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddProductsToCartFromWishlistUsingSidebarTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddProductsToCartFromWishlistUsingSidebarTest.xml new file mode 100644 index 0000000000000..16a18dd27b123 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddProductsToCartFromWishlistUsingSidebarTest.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddProductsToCartFromWishlistUsingSidebarTest"> + <annotations> + <features value="Wishlist"/> + <stories value="Add to wishlist"/> + <title value="Add products from the wishlist to the cart using the sidebar."/> + <description value="Products added to the cart from wishlist and a customer remains on the same page."/> + <group value="wishlist"/> + <severity value="AVERAGE"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryFirst"/> + <createData entity="SimpleSubCategory" stepKey="categorySecond"/> + <createData entity="SimpleProduct" stepKey="simpleProduct1"> + <requiredEntity createDataKey="categoryFirst"/> + </createData> + <createData entity="SimpleProduct" stepKey="simpleProduct2"> + <requiredEntity createDataKey="categorySecond"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="categoryFirst" stepKey="deleteCategoryFirst"/> + <deleteData createDataKey="categorySecond" stepKey="deleteCategorySecond"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + <!-- Sign in as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + <!-- Add product from first category to the wishlist --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryFirst.name$$)}}" stepKey="navigateToCategoryFirstPage"/> + <actionGroup ref="StorefrontCheckCategorySimpleProduct" stepKey="browseAssertCategoryProduct1"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerAddCategoryProductToWishlistActionGroup" stepKey="addSimpleProduct1ToWishlist"> + <argument name="productVar" value="$$simpleProduct1$$"/> + </actionGroup> + <!--Add product to the cart from the Wishlist using the sidebar from the second category page--> + <amOnPage url="{{StorefrontCategoryPage.url($$categorySecond.name$$)}}" stepKey="navigateToCategorySecondPage"/> + <actionGroup ref="StorefrontSwitchCategoryViewToListMode" stepKey="switchCategoryViewToListMode"/> + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSimpleProduct1InWishlistSidebar"> + <argument name="productVar" value="$$simpleProduct1$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerAddProductToCartFromWishlistUsingSidebar" stepKey="addProduct1ToCartFromWishlistUsingSidebar"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <!--Check that a customer on the same page as before--> + <!--hardcoded URL because this method does not support replacement--> + <seeCurrentUrlMatches regex="~\/$$categorySecond.name$$\.html\?(\S+)?\w+=list(&\S+)?$~i" stepKey="seeCurrentCategoryUrlMatches"/> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml index 3ddb9d87073f3..0ae2b6af804bd 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeletePersistedWishlistTest.xml @@ -6,7 +6,7 @@ */ --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontDeletePersistedWishlistTest"> <annotations> <features value="Wishlist"/> @@ -14,6 +14,11 @@ <title value="Customer should be able to delete a persistent wishlist"/> <description value="Customer should be able to delete a persistent wishlist"/> <group value="wishlist"/> + <group value="skip"/> + <skip> + <issueId value="MQE-1145"/> + </skip> + <severity value="AVERAGE"/> </annotations> <before> <createData stepKey="category" entity="SimpleSubCategory"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontRemoveProductsFromWishlistUsingSidebarTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontRemoveProductsFromWishlistUsingSidebarTest.xml new file mode 100644 index 0000000000000..e3382dc41d27e --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontRemoveProductsFromWishlistUsingSidebarTest.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontRemoveProductsFromWishlistUsingSidebarTest"> + <annotations> + <features value="Wishlist"/> + <stories value="Remove from wishlist"/> + <title value="Remove products from the wishlist using the sidebar."/> + <description value="Products removed from wishlist and a customer remains on the same page."/> + <group value="wishlist"/> + <severity value="AVERAGE"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="categoryFirst"/> + <createData entity="SimpleSubCategory" stepKey="categorySecond"/> + <createData entity="SimpleProduct" stepKey="simpleProduct1"> + <requiredEntity createDataKey="categoryFirst"/> + </createData> + <createData entity="SimpleProduct" stepKey="simpleProduct2"> + <requiredEntity createDataKey="categorySecond"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="categoryFirst" stepKey="deleteCategoryFirst"/> + <deleteData createDataKey="categorySecond" stepKey="deleteCategorySecond"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + <!-- Sign in as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + <!-- Add product from first category to the wishlist --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryFirst.name$$)}}" stepKey="navigateToCategoryFirstPage"/> + <actionGroup ref="StorefrontCheckCategorySimpleProduct" stepKey="browseAssertCategoryProduct1"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerAddCategoryProductToWishlistActionGroup" stepKey="addSimpleProduct1ToWishlist"> + <argument name="productVar" value="$$simpleProduct1$$"/> + </actionGroup> + <!--Remove product from the Wishlist using the sidebar from the second category page--> + <amOnPage url="{{StorefrontCategoryPage.url($$categorySecond.name$$)}}" stepKey="navigateToCategorySecondPage"/> + <actionGroup ref="StorefrontSwitchCategoryViewToListMode" stepKey="switchCategoryViewToListMode"/> + <actionGroup ref="StorefrontCustomerCheckProductInWishlistSidebar" stepKey="checkSimpleProduct1InWishlistSidebar"> + <argument name="productVar" value="$$simpleProduct1$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerRemoveProductFromWishlistUsingSidebar" stepKey="removeProduct1FromWishlistUsingSidebar"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <!--Check that a customer on the same page as before--> + <!--hardcoded URL because this method does not support replacement--> + <seeCurrentUrlMatches regex="~\/$$categorySecond.name$$\.html\?(\S+)?\w+=list(&\S+)?$~i" stepKey="seeCurrentCategoryUrlMatches"/> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/Test/Mftf/composer.json b/app/code/Magento/Wishlist/Test/Mftf/composer.json deleted file mode 100644 index d016f9346f60c..0000000000000 --- a/app/code/Magento/Wishlist/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-wishlist", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-rss": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-wishlist-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php index 6260a1292bee9..bb4ae44fcc31d 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/RemoveTest.php @@ -315,13 +315,11 @@ public function testExecuteCanNotSaveWishlist() ->with(\Magento\Wishlist\Model\Item::class) ->willReturn($item); - $this->request - ->expects($this->once()) - ->method('getServer') - ->with('HTTP_REFERER') + $this->redirect + ->method('getRefererUrl') + ->with() ->willReturn($referer); $this->request - ->expects($this->exactly(3)) ->method('getParam') ->willReturnMap( [ @@ -401,12 +399,6 @@ public function testExecuteCanNotSaveWishlistAndWithRedirect() ->willReturn($item); $this->request - ->expects($this->once()) - ->method('getServer') - ->with('HTTP_REFERER') - ->willReturn($referer); - $this->request - ->expects($this->exactly(3)) ->method('getParam') ->willReturnMap( [ diff --git a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php index d2e81b0236ce8..4954712e5ff3b 100644 --- a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php @@ -8,7 +8,7 @@ use Magento\Catalog\Helper\Image; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Type\AbstractType; -use Magento\Catalog\Pricing\Price\ConfiguredPriceInterface; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; use Magento\Framework\App\ViewInterface; use Magento\Framework\Pricing\Render; use Magento\Wishlist\Block\Customer\Sidebar; @@ -24,19 +24,22 @@ class WishlistTest extends \PHPUnit\Framework\TestCase { /** @var Wishlist */ - protected $model; + private $model; /** @var Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $wishlistHelperMock; + private $wishlistHelperMock; /** @var Sidebar|\PHPUnit_Framework_MockObject_MockObject */ - protected $sidebarMock; + private $sidebarMock; /** @var Image|\PHPUnit_Framework_MockObject_MockObject */ - protected $catalogImageHelperMock; + private $catalogImageHelperMock; /** @var ViewInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $viewMock; + private $viewMock; + + /** @var \Magento\Catalog\Block\Product\ImageBuilder|\PHPUnit_Framework_MockObject_MockObject */ + private $itemResolver; protected function setUp() { @@ -60,11 +63,16 @@ protected function setUp() ->method('create') ->willReturn($this->catalogImageHelperMock); + $this->itemResolver = $this->createMock( + ItemResolverInterface::class + ); + $this->model = new Wishlist( $this->wishlistHelperMock, $this->sidebarMock, $imageHelperFactory, - $this->viewMock + $this->viewMock, + $this->itemResolver ); } @@ -162,6 +170,10 @@ public function testGetSectionData() ->method('getProduct') ->willReturn($productMock); + $this->itemResolver->expects($this->once()) + ->method('getFinalProduct') + ->willReturn($productMock); + $this->catalogImageHelperMock->expects($this->once()) ->method('init') ->with($productMock, 'wishlist_sidebar_block', []) @@ -237,11 +249,11 @@ public function testGetSectionData() $this->wishlistHelperMock->expects($this->once()) ->method('getAddToCartParams') - ->with($itemMock, true) + ->with($itemMock) ->willReturn($itemAddParams); $this->wishlistHelperMock->expects($this->once()) ->method('getRemoveParams') - ->with($itemMock, true) + ->with($itemMock) ->willReturn($itemRemoveParams); $this->assertEquals($result, $this->model->getSectionData()); @@ -359,6 +371,10 @@ public function testGetSectionDataWithTwoItems() ->method('getProduct') ->willReturn($productMock); + $this->itemResolver->expects($this->exactly(2)) + ->method('getFinalProduct') + ->willReturn($productMock); + $this->catalogImageHelperMock->expects($this->exactly(2)) ->method('init') ->with($productMock, 'wishlist_sidebar_block', []) @@ -435,11 +451,11 @@ public function testGetSectionDataWithTwoItems() $this->wishlistHelperMock->expects($this->exactly(2)) ->method('getAddToCartParams') - ->with($itemMock, true) + ->with($itemMock) ->willReturn($itemAddParams); $this->wishlistHelperMock->expects($this->exactly(2)) ->method('getRemoveParams') - ->with($itemMock, true) + ->with($itemMock) ->willReturn($itemRemoveParams); $this->assertEquals($result, $this->model->getSectionData()); diff --git a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php index 154db1c4a1474..1769306172aab 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php @@ -248,6 +248,7 @@ public function testGetAddToCartParams() $expected = [ 'item' => $wishlistItemId, 'qty' => $wishlistItemQty, + ActionInterface::PARAM_NAME_URL_ENCODED => '', ]; $this->postDataHelper->expects($this->once()) ->method('getPostData') @@ -333,7 +334,7 @@ public function testGetRemoveParams() $this->postDataHelper->expects($this->once()) ->method('getPostData') - ->with($url, ['item' => $wishlistItemId]) + ->with($url, ['item' => $wishlistItemId, ActionInterface::PARAM_NAME_URL_ENCODED => '']) ->willReturn($url); $this->assertEquals($url, $this->model->getRemoveParams($this->wishlistItem)); diff --git a/app/code/Magento/Wishlist/etc/adminhtml/system.xml b/app/code/Magento/Wishlist/etc/adminhtml/system.xml index 86c24bab11ffb..50ea4b01fe12b 100644 --- a/app/code/Magento/Wishlist/etc/adminhtml/system.xml +++ b/app/code/Magento/Wishlist/etc/adminhtml/system.xml @@ -22,12 +22,12 @@ <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model> </field> - <field id="number_limit" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="number_limit" translate="label comment" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Max Emails Allowed to be Sent</label> <comment>10 by default. Max - 10000</comment> <validate>validate-digits validate-digits-range digits-range-1-10000</validate> </field> - <field id="text_limit" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="text_limit" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Email Text Length Limit</label> <comment>255 by default</comment> <validate>validate-digits validate-digits-range digits-range-1-10000</validate> diff --git a/app/code/Magento/Wishlist/etc/db_schema_whitelist.json b/app/code/Magento/Wishlist/etc/db_schema_whitelist.json index 82791ad70958f..beaab64280a76 100644 --- a/app/code/Magento/Wishlist/etc/db_schema_whitelist.json +++ b/app/code/Magento/Wishlist/etc/db_schema_whitelist.json @@ -1,55 +1,55 @@ { - "wishlist": { - "column": { - "wishlist_id": true, - "customer_id": true, - "shared": true, - "sharing_code": true, - "updated_at": true + "wishlist": { + "column": { + "wishlist_id": true, + "customer_id": true, + "shared": true, + "sharing_code": true, + "updated_at": true + }, + "index": { + "WISHLIST_SHARED": true + }, + "constraint": { + "PRIMARY": true, + "WISHLIST_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, + "WISHLIST_CUSTOMER_ID": true + } }, - "index": { - "WISHLIST_SHARED": true + "wishlist_item": { + "column": { + "wishlist_item_id": true, + "wishlist_id": true, + "product_id": true, + "store_id": true, + "added_at": true, + "description": true, + "qty": true + }, + "index": { + "WISHLIST_ITEM_WISHLIST_ID": true, + "WISHLIST_ITEM_PRODUCT_ID": true, + "WISHLIST_ITEM_STORE_ID": true + }, + "constraint": { + "PRIMARY": true, + "WISHLIST_ITEM_WISHLIST_ID_WISHLIST_WISHLIST_ID": true, + "WISHLIST_ITEM_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, + "WISHLIST_ITEM_STORE_ID_STORE_STORE_ID": true, + "WISHLIST_ITEM_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true + } }, - "constraint": { - "PRIMARY": true, - "WISHLIST_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID": true, - "WISHLIST_CUSTOMER_ID": true + "wishlist_item_option": { + "column": { + "option_id": true, + "wishlist_item_id": true, + "product_id": true, + "code": true, + "value": true + }, + "constraint": { + "PRIMARY": true, + "FK_A014B30B04B72DD0EAB3EECD779728D6": true + } } - }, - "wishlist_item": { - "column": { - "wishlist_item_id": true, - "wishlist_id": true, - "product_id": true, - "store_id": true, - "added_at": true, - "description": true, - "qty": true - }, - "index": { - "WISHLIST_ITEM_WISHLIST_ID": true, - "WISHLIST_ITEM_PRODUCT_ID": true, - "WISHLIST_ITEM_STORE_ID": true - }, - "constraint": { - "PRIMARY": true, - "WISHLIST_ITEM_WISHLIST_ID_WISHLIST_WISHLIST_ID": true, - "WISHLIST_ITEM_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID": true, - "WISHLIST_ITEM_STORE_ID_STORE_STORE_ID": true, - "WISHLIST_ITEM_PRODUCT_ID_SEQUENCE_PRODUCT_SEQUENCE_VALUE": true - } - }, - "wishlist_item_option": { - "column": { - "option_id": true, - "wishlist_item_id": true, - "product_id": true, - "code": true, - "value": true - }, - "constraint": { - "PRIMARY": true, - "FK_A014B30B04B72DD0EAB3EECD779728D6": true - } - } } \ No newline at end of file diff --git a/app/code/Magento/Wishlist/etc/frontend/di.xml b/app/code/Magento/Wishlist/etc/frontend/di.xml index 00642b132bfdf..f28e85fff0a15 100644 --- a/app/code/Magento/Wishlist/etc/frontend/di.xml +++ b/app/code/Magento/Wishlist/etc/frontend/di.xml @@ -44,6 +44,12 @@ <item name="template" xsi:type="string">Magento_Wishlist::messages/addProductSuccessMessage.phtml</item> </item> </item> + <item name="removeWishlistItemSuccessMessage" xsi:type="array"> + <item name="renderer" xsi:type="const">\Magento\Framework\View\Element\Message\Renderer\BlockRenderer::CODE</item> + <item name="data" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Wishlist::messages/removeWishlistItemSuccessMessage.phtml</item> + </item> + </item> </argument> </arguments> </type> diff --git a/app/code/Magento/Wishlist/i18n/en_US.csv b/app/code/Magento/Wishlist/i18n/en_US.csv index 75d5a2e840102..a1d33cbd574f0 100644 --- a/app/code/Magento/Wishlist/i18n/en_US.csv +++ b/app/code/Magento/Wishlist/i18n/en_US.csv @@ -99,7 +99,9 @@ Back,Back "Email Template","Email Template" "Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected." "Max Emails Allowed to be Sent","Max Emails Allowed to be Sent" +"10 by default. Max - 10000","10 by default. Max - 10000" "Email Text Length Limit","Email Text Length Limit" +"255 by default","255 by default" "General Options","General Options" Enabled,Enabled "My Wish List Link","My Wish List Link" diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/image.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/image.phtml index 1008f5f377df5..2eb4664991e99 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/image.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/image.phtml @@ -13,5 +13,5 @@ $item = $block->getItem(); $product = $item->getProduct(); ?> <a class="product-item-photo" tabindex="-1" href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>" title="<?= $block->escapeHtmlAttr($product->getName()) ?>"> - <?= $block->getImage($product, 'wishlist_thumbnail')->toHtml() ?> + <?= $block->getImage($block->getProductForThumbnail($item), 'wishlist_thumbnail')->toHtml() ?> </a> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/messages/removeWishlistItemSuccessMessage.phtml b/app/code/Magento/Wishlist/view/frontend/templates/messages/removeWishlistItemSuccessMessage.phtml new file mode 100644 index 0000000000000..1196b8fec4a21 --- /dev/null +++ b/app/code/Magento/Wishlist/view/frontend/templates/messages/removeWishlistItemSuccessMessage.phtml @@ -0,0 +1,11 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +// @codingStandardsIgnoreFile +/** @var \Magento\Framework\View\Element\Template $block */ +?> + +<?= $block->escapeHtml(__('%1 has been removed from your Wish List.', $block->getData('product_name'))); ?> + diff --git a/app/code/Magento/Wishlist/view/frontend/templates/sidebar.phtml b/app/code/Magento/Wishlist/view/frontend/templates/sidebar.phtml index 00fb87b411fe0..84b607adb6362 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/sidebar.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/sidebar.phtml @@ -31,7 +31,7 @@ $wishlistHelper = $this->helper('Magento\Wishlist\Helper\Data'); <div class="product-item-details"> <strong class="product-item-name"> <a data-bind="attr: { href: product_url }" class="product-item-link"> - <span data-bind="text: product_name"></span> + <span data-bind="html: product_name"></span> </a> </strong> <div data-bind="html: product_price"></div> diff --git a/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json b/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 6969178cfdaa6..0000000000000 --- a/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-wishlist-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less index 45f1a835d18d8..6d21462d753ca 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Analytics/web/css/source/_module.less @@ -18,65 +18,65 @@ // _____________________________________________ .dashboard-advanced-reports { - .lib-vendor-prefix-display(flex); - border-color: @color-gray89; - border-style: solid; - border-width: 1px 0; - margin-bottom: @indent__l; - padding: @indent__m 0; + .lib-vendor-prefix-display(flex); + border-color: @color-gray89; + border-style: solid; + border-width: 1px 0; + margin-bottom: @indent__l; + padding: @indent__m 0; } .dashboard-advanced-reports-title { - &:extend(.dashboard-item-title all); - margin-bottom: @indent__s; + &:extend(.dashboard-item-title all); + margin-bottom: @indent__s; } .dashboard-advanced-reports-content { - line-height: @line-height__xl; + line-height: @line-height__xl; } .dashboard-advanced-reports-actions { - .lib-vendor-prefix-flex-basis(auto); - .lib-vendor-prefix-flex-grow(1); - .lib-vendor-prefix-flex-shrink(1); - align-self: center; - margin-left: @indent__m; - margin-right: @page-main-actions__padding; - text-align: right; + .lib-vendor-prefix-flex-basis(auto); + .lib-vendor-prefix-flex-grow(1); + .lib-vendor-prefix-flex-shrink(1); + align-self: center; + margin-left: @indent__m; + margin-right: @page-main-actions__padding; + text-align: right; } .action-advanced-reports { - &:extend(.abs-action-l all); - &:extend(.abs-action-pattern all); - background-color: @button-advanced-reports__background-color; - border-color: @button-advanced-reports__background-color; - color: @button-advanced-reports__color; - text-shadow: 1px 1px 0 rgba(0, 0, 0, .25); - white-space: nowrap; - - &:after { - &:extend(.abs-icon all); - content: @icon-external-link__content; - font-size: @font-size__xs; - vertical-align: super; - } - - &:hover, - &:active, - &:focus { - background-color: @button-advanced-reports__hover__background-color; - border-color: @button-advanced-reports__hover__border-color; - box-shadow: @button__hover__box-shadow; + &:extend(.abs-action-l all); + &:extend(.abs-action-pattern all); + background-color: @button-advanced-reports__background-color; + border-color: @button-advanced-reports__background-color; color: @button-advanced-reports__color; - text-decoration: none; - } - - &.disabled, - &[disabled] { - cursor: default; - opacity: @disabled__opacity; - pointer-events: none; - } + text-shadow: 1px 1px 0 rgba(0, 0, 0, .25); + white-space: nowrap; + + &:after { + &:extend(.abs-icon all); + content: @icon-external-link__content; + font-size: @font-size__xs; + vertical-align: super; + } + + &:hover, + &:active, + &:focus { + background-color: @button-advanced-reports__hover__background-color; + border-color: @button-advanced-reports__hover__border-color; + box-shadow: @button__hover__box-shadow; + color: @button-advanced-reports__color; + text-decoration: none; + } + + &.disabled, + &[disabled] { + cursor: default; + opacity: @disabled__opacity; + pointer-events: none; + } } // @@ -84,42 +84,42 @@ // --------------------------------------------- .advanced-reports-subscription-modal { - .modal-inner-wrap { - max-width: 75rem; - margin-top: 13rem; - - .modal-content, .modal-header { - padding-left: 4rem; - padding-right: 4rem; - - .action-close { - display: none; - } + .modal-inner-wrap { + margin-top: 13rem; + max-width: 75rem; + + .modal-content, + .modal-header { + padding-left: 4rem; + padding-right: 4rem; + + .action-close { + display: none; + } + } } - } - .admin__fieldset { - padding: 0; - } + .admin__fieldset { + padding: 0; + } } .advanced-reports-subscription-text { - line-height: @line-height__xl; - padding-bottom: 8rem; + line-height: @line-height__xl; + padding-bottom: 8rem; } .advanced-reports-subscription-close { - display: inline-block; - vertical-align: top; - float: right; + display: block; + float: right; } .advanced-reports-subscription-modal { - h1:first-of-type { - background: url("Magento_Analytics::images/analytics-icon.svg") no-repeat; - background-size: 55px 49.08px; - padding: 1.5rem 0 2rem 7rem; - } + h1:first-of-type { + background: url("Magento_Analytics::images/analytics-icon.svg") no-repeat; + background-size: 55px 49.08px; + padding: 1.5rem 0 2rem 7rem; + } } // @@ -127,41 +127,44 @@ // _____________________________________________ .config-additional-comment { - border-color: @color-gray80; - border-style: solid; - border-width: 1px 0; - margin: @indent__l 0; - padding: @indent__m; + border-color: @color-gray80; + border-style: solid; + border-width: 1px 0; + margin: @indent__l 0; + padding: @indent__m; } .config-additional-comment-title { - margin-bottom: @indent__xs; + margin-bottom: @indent__xs; } .config-additional-comment-content { - line-height: @line-height__l; + line-height: @line-height__l; } .config-vertical-title { - clear: both; - color: #303030; - font-size: 1.7rem; - font-weight: 600; - letter-spacing: .025em; - padding: 1.9rem 2.8rem 1.9rem 0; - position: relative; + clear: both; + color: rgb(48, 48, 48); + font-size: 1.7rem; + font-weight: 600; + letter-spacing: .025em; + padding: 1.9rem 2.8rem 1.9rem 0; + position: relative; } .config-vertical-comment { - line-height: 1.5; - margin-bottom: .5em; - margin-top: 1rem; + line-height: 1.5; + margin-bottom: .5em; + margin-top: 1rem; } +/** + * @codingStandardsIgnoreStart + */ #row_analytics_general_vertical { - >td.config-vertical-label { - >label.admin__field-label { - padding-right: 0; + >td.config-vertical-label { + >label.admin__field-label { + padding-right: 0; + } } - } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/layout/default.xml b/app/design/adminhtml/Magento/backend/Magento_Backend/layout/default.xml index da16bde107673..337d63369b160 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/layout/default.xml +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/layout/default.xml @@ -7,6 +7,7 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <head> + <meta name="robots" content="NOINDEX,NOFOLLOW"/> <css src="jquery/jstree/themes/default/style.css"/> <css src="css/styles-old.css"/> <css src="css/styles.css"/> diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module-old.less index 6937855492263..c4bed53dcbe80 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/_module-old.less @@ -14,7 +14,7 @@ display: inline-block; vertical-align: top; width: 100%; - background-color: #fff; + background-color: @color-white; border: 1px solid #ada89e; border-radius: 2px; @@ -54,7 +54,6 @@ .search-global-field .mage-suggest { position: static; display: block; - vertical-align: baseline; width: auto; background-color: transparent; border: none; @@ -72,7 +71,7 @@ top: 100%; margin: 1px -1px 0 -1px; border: 1px solid #cac2b5; - background: #fff; + background: @color-white; box-shadow: 0 2px 4px rgba(0, 0, 0, .2); z-index: 990; @@ -121,7 +120,7 @@ } .mage-suggest-selected > a { - color: #000; + color: @color-black; background: #F1FFEB; } } @@ -177,7 +176,7 @@ } &:before { - display: inline-block; + display: block; float: right; margin-left: 4px; font-size: 13px; @@ -229,7 +228,7 @@ > input.ui-autocomplete-loading, > input.mage-suggest-state-loading { - background: #fff url("@{baseDir}mui/images/ajax-loader-small.gif") no-repeat 190px 50%; + background: @color-white url("@{baseDir}mui/images/ajax-loader-small.gif") no-repeat 190px 50%; } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less index 8825f4ea3f5a2..ba28108326006 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less @@ -187,12 +187,12 @@ display: block; } - // External link marker + // External link marker [target='_blank'] { &:after { &:extend(.abs-icon all); content: @icon-external-link__content; - font-size: 0.5rem; + font-size: .5rem; margin-left: @indent__xs; vertical-align: super; } @@ -263,7 +263,6 @@ visibility: hidden; z-index: @submenu__z-index - 1; - .ie10 &, .ie11 & { height: 100%; } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_login.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_login.less index 97ed1d8cdb1ec..e7f4c41de9750 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_login.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_login.less @@ -49,10 +49,6 @@ position: relative; width: 100%; z-index: 1; - - .ie9 & { - margin-top: 10%; - } } :-ms-input-placeholder { @@ -84,7 +80,7 @@ } .messages { - margin-top: 0.5rem; + margin-top: .5rem; + form .admin__legend { display: none; diff --git a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module-old.less index eefbc11201d9c..2074106e719db 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module-old.less @@ -53,7 +53,7 @@ } .attribute-change-checkbox { - background: #fff; + background: @color-white; display: block; margin-top: 5px; diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/_module-old.less index 865ece970465d..73e1bcf4a7d58 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/_module-old.less @@ -16,7 +16,7 @@ ); .action.toggle { - background: #fff; + background: @color-white; border-radius: 0 1px 1px 0; border: 1px solid #ada89e; height: 33px; @@ -58,7 +58,7 @@ .action.toggle { padding: 0 3px; border: 1px solid #b7b2a7; - background: #fff; + background: @color-white; border-radius: 0 1px 1px 0; border-left: none; height: 50px; @@ -121,7 +121,7 @@ width: auto; .addafter { - background: #fff; + background: @color-white; border-width: 1px 0 1px 1px; direction: ltr; height: 33px; diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less index f3bb1fede2512..659b1fa811db1 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less @@ -18,8 +18,8 @@ // _____________________________________________ .currency-addon { + border: 1px solid rgb(173,173,173); position: relative; - border: 1px solid #adadad; display: -webkit-inline-flex; display: -ms-inline-flexbox; -webkit-flex-direction: row; @@ -30,17 +30,18 @@ width: 100%; .admin__control-text { - appearence: none; - -webkit-flex-grow: 1; - flex-grow: 1; -ms-flex-order: 1; - -webkit-order: 1; - order: 1; + -webkit-appearance: none; + -webkit-flex-grow: 1; -webkit-flex-shrink: 1; - flex-shrink: 1; + -webkit-order: 1; + appearance: none; background-color: transparent; border-color: transparent; box-shadow: none; + flex-grow: 1; + flex-shrink: 1; + order: 1; vertical-align: top; &:focus { @@ -51,28 +52,28 @@ } label.error { - position: absolute; left: 0; + position: absolute; top: 33px; } .currency-symbol { + -webkit-flex-basis: auto; + -webkit-flex-grow: 0; + -webkit-flex-shrink: 0; border: solid @currency-addon-symbol__border-color; border-width: 0; box-sizing: border-box; color: @currency-addon-symbol__color; + flex-basis: auto; + flex-grow: 0; + flex-shrink: 0; height: @currency-addon-symbol__height; + order: 0; padding: 7px 0 0 @indent__xs; position: static; transition: @smooth__border-color; - -webkit-flex-basis: auto; - flex-basis: auto; - -webkit-flex-grow: 0; - flex-grow: 0; - -webkit-flex-shrink: 0; - flex-shrink: 0; z-index: 1; - order: 0; } ._error & { diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_bulk-images.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_bulk-images.less index b2dc94d9ffa74..35dae432a6c4f 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_bulk-images.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/steps/_bulk-images.less @@ -97,8 +97,8 @@ margin-left: 0; .admin__field-control { + display: block; float: right; - display: inline-block; } } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Enterprise/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_Enterprise/web/css/source/_module-old.less index 0eba6b0d361f8..3a7b6d30c914d 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Enterprise/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_Enterprise/web/css/source/_module-old.less @@ -299,7 +299,7 @@ .invitee_information .data-table tbody tr th, .inviter_information .data-table tbody tr td, .inviter_information .data-table tbody tr th { - background-color: #fff; + background-color: @color-white; border: 0; color: #666; padding: 9px 10px 10px; diff --git a/app/design/adminhtml/Magento/backend/Magento_GiftRegistry/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_GiftRegistry/web/css/source/_module-old.less index 6b0d73299d903..3e1a1b9cf9be9 100644 --- a/app/design/adminhtml/Magento/backend/Magento_GiftRegistry/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_GiftRegistry/web/css/source/_module-old.less @@ -7,7 +7,3 @@ // Stores -> Gift Registry // --------------------------------------------- -.eq-ie9 [class^=' adminhtml-giftregistry-'] .custom-options .data-table { - table-layout: auto; - word-wrap: normal; -} diff --git a/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less index d4f918567e579..92a08eb7909c4 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Marketplace/web/css/source/_module.less @@ -15,22 +15,12 @@ .lib-vendor-prefix-display(flex); .lib-vendor-prefix-flex-direction(row); .lib-vendor-prefix-flex-wrap(wrap); - - .ie9 & { - word-spacing: -.45rem; - } } .partner { margin-bottom: @indent__xl; padding: 0 @indent__m; width: 100% / 3; - - .ie9 & { - display: inline-block; - vertical-align: top; - word-spacing: normal; - } } } diff --git a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less index a7b6f553f1ff8..6f1e4224fa7a5 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_ReleaseNotification/web/css/source/_module.less @@ -8,111 +8,112 @@ // --------------------------------------------- .release-notification-modal { - -webkit-transition: visibility 0s .5s, opacity .5s ease; - transition: visibility 0s .5s, opacity .5s ease; - - &._show { - visibility: visible; - opacity: 1; - -webkit-transition: opacity .5s ease; - transition: opacity .5s ease; - } - - .modal-inner-wrap { - -webkit-transform: translateX(0); - transform: translateX(0); - -webkit-transition: -webkit-transform 0s; - transition: transform 0s; - height: 50rem; - max-width: 75rem; - margin-top: 13rem; - - .modal-content, .modal-header { - padding-left: 4rem; - padding-right: 4rem; - - .action-close { - display: none; - } + -webkit-transition: visibility 0s .5s, opacity .5s ease; + transition: visibility 0s .5s, opacity .5s ease; + + &._show { + -webkit-transition: opacity .5s ease; + opacity: 1; + transition: opacity .5s ease; + visibility: visible; } - } - .admin__fieldset { - padding: 0; - } + .modal-inner-wrap { + .modal-content, + .modal-header { + padding-left: 4rem; + padding-right: 4rem; + + .action-close { + display: none; + } + } + + -webkit-transform: translateX(0); + -webkit-transition: -webkit-transform 0s; + height: 50rem; + transition: transform 0s; + transform: translateX(0); + margin-top: 13rem; + max-width: 75rem; + } + + .admin__fieldset { + padding: 0; + } } .release-notification-title-with-image { - padding: 1.5rem 0 2rem 7rem; - background-size: 55px 49.08px; - background-repeat: no-repeat; + background-repeat: no-repeat; + background-size: 55px 49.08px; + padding: 1.5rem 0 2rem 7rem; } .release-notification-text { - line-height: @line-height__l; - - ul { - margin: 2rem 0 2rem 0; - - li { - font-size: @font-size__xs; - margin: 1.5rem 0 1.5rem 2rem; - - span { - font-size: @font-size__base; - vertical-align: middle; - position: relative; - left: 1rem; - } + line-height: @line-height__l; + + ul { + margin: 2rem 0 2rem 0; + + li { + font-size: @font-size__xs; + margin: 1.5rem 0 1.5rem 2rem; + + span { + font-size: @font-size__base; + vertical-align: middle; + position: relative; + left: 1rem; + } + } } - } } -.release-notification-button-next, .release-notification-button-back { - display: inline-block; - vertical-align: top; - float: right; - position: absolute; - bottom: 4rem; +.release-notification-button-next, +.release-notification-button-back { + bottom: 4rem; + display: block; + float: right; + position: absolute; } .release-notification-button-next { - .lib-button-as-link(); - right: 4rem; - font-weight: 400; + .lib-button-as-link(); + font-weight: 400; + right: 4rem; } .release-notification-button-back { - .lib-button-as-link(); - left: 4rem; - font-weight: 400; + .lib-button-as-link(); + font-weight: 400; + left: 4rem; } .highlight-item { - padding: 0 0 2rem 8.5rem; - margin-left: 1rem; - background-size: 65px 58px; - background-repeat: no-repeat; - - h3 { - margin: 0; - - span { - font-style: @font-style__emphasis; - font-size: @font-size__s; - font-weight: @font-weight__light; + h3 { + margin: 0; + + span { + font-size: @font-size__s; + font-style: @font-style__emphasis; + font-weight: @font-weight__light; + } } - } + + background-size: 65px 58px; + background-repeat: no-repeat; + padding: 0 0 2rem 8.5rem; + margin-left: 1rem; } .highlight-item-no-image { - padding: 0 0 2rem 0; + padding: 0 0 2rem 0; - h3 { - margin: 0; - } + h3 { + margin: 0; + } } .hide-release-notification { - display: none; + display: none; } diff --git a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less index a71731320c5ce..3e1f4e75031d2 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less +++ b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less @@ -1,5 +1,5 @@ // /** -// * Copyright © 2015 Magento. All rights reserved. +// * Copyright © Magento, Inc. All rights reserved. // * See COPYING.txt for license details. // */ @@ -366,7 +366,7 @@ // Generic data grid .admin__data-grid-outer-wrap { border-top: 1px solid @staging-preview-table-dark__border-color; - max-height: 400px; // ToDO: remove after JS adjustment implemented + max-height: 400px; // ToDO remove after JS adjustment implemented overflow-y: auto; padding: 15px @indent__s 0 0; } @@ -730,15 +730,9 @@ top: @indent__l; width: 1px; - .ie9 &, - .ie10 &, .ie11 & { height: 1000px; } - - .ie9 & { - border-right: 1px dashed @staging-preview-table-lighten__border-color; - } } } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/_module-old.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/_module-old.less index 17910d7c8e5b4..3756fe678a3c9 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/_module-old.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/_module-old.less @@ -78,7 +78,7 @@ &.ui-state-active { z-index: 2; - background: #FFF; + background: @color-white; padding: 1px (@spacing-m - 1px); border: 2px solid #eb5202; margin: -1px; @@ -180,14 +180,14 @@ .lib-clearfix(); [data-part=left] { - display: inline-block; + display: block; width: 45%; float: left; text-align: left; } [data-part=right] { - display: inline-block; + display: block; width: 45%; text-align: right; float: right; diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less index 065f870dbfe01..d55608ade4a05 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less @@ -59,18 +59,6 @@ margin-top: -(@data-grid-spinner__size / 2); position: absolute; top: 50%; - - .ie9 & { - background: url('@{baseDir}images/loader-2.gif') 50% 50% no-repeat; - bottom: 0; - height: 149px; - left: 0; - margin: auto; - position: absolute; - right: 0; - top: 0; - width: 218px; - } } } @@ -999,10 +987,6 @@ body._in-resize { transform: rotate(45deg); width: 1.6rem; z-index: 3; - - .ie9 & { - display: none; - } } } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-action-bookmarks.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-action-bookmarks.less index dfaf340f90b6c..f9c93dce57002 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-action-bookmarks.less +++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-action-bookmarks.less @@ -132,10 +132,6 @@ font-size: @action-dropdown-menu__font-size; min-width: 15rem; width: ~'calc(100% - 4rem)'; - - .ie9 & { - width: 15rem; - } } .action-dropdown-menu-item-actions { diff --git a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less index 909935e1f8ea8..1cd867efdd13b 100644 --- a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less @@ -68,7 +68,7 @@ a { color: @color-gray85; - display: inline-block; + display: block; float: left; text-decoration: none; } diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_buttons.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_buttons.less index ec7509cba6dda..2c60af8dfa178 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_buttons.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_buttons.less @@ -73,12 +73,6 @@ cursor: default; opacity: @disabled__opacity; pointer-events: none; - - .ie9 & { - background-color: @btn__base__disabled__background-color; - opacity: 1; - text-shadow: none; - } } } @@ -135,22 +129,6 @@ ); color: @btn-prime__color; } - - // Disabled state for IE9 - &[disabled], - &.disabled { - .ie9 & { - background-color: @btn-prime__disabled__background-color; - } - - &:hover, - &:active { - .ie9 & { - background-color: @btn-prime__disabled__background-color; - filter: none; - } - } - } } .btn-secondary { @@ -167,21 +145,6 @@ background-color: @btn-secondary__active__background-color; color: @btn-secondary__color; } - - // Disabled state for IE9 - &[disabled], - &.disabled { - .ie9 & { - background-color: @btn-secondary__disabled__background-color; - } - - &:active { - .ie9 & { - background-color: @btn-secondary__disabled__background-color; - filter: none; - } - } - } } // @@ -239,26 +202,6 @@ left: 1px; } } - - // Disabled state for IE9 - &[disabled], - &.disabled { - &:after { - .ie9 & { - border-color: transparent transparent transparent @btn__base__disabled__background-color; - } - } - - &:focus, - &:hover, - &:active { - &:after { - .ie9 & { - border-left-color: @btn__base__disabled__background-color; - } - } - } - } } .btn-prime { @@ -285,25 +228,6 @@ left: 1px; } } - - // Disabled state for IE9 - &[disabled], - &.disabled { - &:after { - .ie9 & { - border-color: transparent transparent transparent @btn-prime__disabled__background-color; - } - } - - &:hover, - &:active { - &:after { - .ie9 & { - border-left-color: @btn-prime__disabled__background-color; - } - } - } - } } } @@ -342,25 +266,6 @@ right: 1px; } } - - // Disabled state for IE9 - &[disabled], - &.disabled { - &:after { - .ie9 & { - border-color: transparent @btn__base__disabled__background-color transparent transparent; - } - } - - &:hover, - &:active { - &:after { - .ie9 & { - border-right-color: @btn__base__disabled__background-color; - } - } - } - } } .btn-prime { @@ -387,25 +292,6 @@ right: 1px; } } - - // Disabled state for IE9 - &[disabled], - &.disabled { - &:after { - .ie9 & { - border-color: transparent @btn-prime__disabled__background-color transparent transparent; - } - } - - &:hover, - &:active { - &:after { - .ie9 & { - border-right-color: @btn-prime__disabled__background-color; - } - } - } - } } } diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/forms/_selects.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/forms/_selects.less index 7e5d34e48892a..76973802a3d2e 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/forms/_selects.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/forms/_selects.less @@ -47,10 +47,6 @@ top: 0; width: @select-check__size; z-index: -2; - - .ie9 & { - display: none; - } } &:before { @@ -66,10 +62,6 @@ top: 50%; width: 0; z-index: -1; - - .ie9 & { - display: none; - } } .form-el-select { @@ -82,11 +74,6 @@ padding: @form-el__padding-top ~'calc(@{select-check__size} + 10%)' @form-el__padding-bottom @form-el__padding-side; width: 110%; - .ie9 & { - padding-right: @form-el__padding-side; - width: 100%; - } - &::-ms-expand { display: none; } diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/pages/_common.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/pages/_common.less index b71c94f2917c8..2b33d1fa542b6 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/pages/_common.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/pages/_common.less @@ -19,12 +19,6 @@ padding-top: @main__indent-top; } -.menu-wrapper { - .logo-static { - pointer-events: none; - } -} - // // Header // _____________________________________________ diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_actions.less b/app/design/adminhtml/Magento/backend/web/css/source/_actions.less index 4377687fd3d39..886bbcc29a3b9 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_actions.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_actions.less @@ -23,8 +23,15 @@ } .abs-action-pattern { + &[disabled], + &.disabled { + cursor: default; + opacity: @disabled__opacity; + pointer-events: none; + } + border: @button__border-size @button__border-style; - border-radius: 0; // ToDo UI: Delete with admin scope + border-radius: 0; // ToDo UI Delete with admin scope display: inline-block; font-family: @button__font-family; font-size: @button__font-size; @@ -33,13 +40,6 @@ padding: @button__padding-top @button__padding-horizontal @button__padding-bottom; text-align: center; vertical-align: baseline; - - &[disabled], - &.disabled { - cursor: default; - opacity: @disabled__opacity; - pointer-events: none; - } } .abs-action-l { @@ -142,16 +142,6 @@ box-shadow: @button__hover__box-shadow; } } - - &[disabled], - &.disabled { - .ie9 &, - .ie10 & { - background-color: @button-triangle__base__disabled__background-color; - opacity: 1; - text-shadow: none; - } - } } } @@ -182,17 +172,6 @@ border-left-color: @button__hover__background-color; } } - - // Disabled state for IE9, IE10 - &.disabled, - &[disabled] { - &:after { - .ie9 &, - .ie10 & { - border-color: transparent transparent transparent @button-triangle__base__disabled__background-color; - } - } - } } .action-primary { @@ -238,17 +217,6 @@ border-right-color: @button__hover__background-color; } } - - // Disabled state for IE9, IE10 - &.disabled, - &[disabled] { - &:after { - .ie9 &, - .ie10 & { - border-color: transparent @button-triangle__base__disabled__background-color transparent transparent; - } - } - } } .action-primary { @@ -433,7 +401,7 @@ button { left: 0; list-style: none; margin: 2px 0 0; // Action box-shadow + 1px indent - min-width: 0; // ToDo UI: Should be deleted with old styles + min-width: 0; // ToDo UI Should be deleted with old styles padding: 0; position: absolute; right: 0; @@ -444,7 +412,7 @@ button { } > li { - border: none; // ToDo UI: Should be deleted with old styles + border: none; // ToDo UI Should be deleted with old styles display: block; padding: 0; transition: background-color .1s linear; @@ -495,11 +463,6 @@ button { position: absolute; right: auto; top: auto; - - .ie9 & { - margin-left: 99%; - margin-top: -3.5rem; - } } a { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_reset.less b/app/design/adminhtml/Magento/backend/web/css/source/_reset.less index 3a2847c82b8ce..51de756bff16c 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_reset.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_reset.less @@ -12,6 +12,7 @@ html { -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; box-sizing: border-box; + text-size-adjust: 100%; } * { @@ -87,8 +88,6 @@ template { // --------------------------------------------- a { - background-color: transparent; // Remove the gray background color from active links in IE 10. - // Improve readability when focused and also mouse hovered in all browsers. &:active, &:hover { @@ -107,7 +106,7 @@ abbr { } } -// Address style set to 'bolder' in Firefox 4+, Safari, and Chrome. +// Address style set to 'bolder' in Firefox 4 and later, Safari, and Chrome. b, strong { font-weight: bold; @@ -206,9 +205,9 @@ input, optgroup, select, textarea { - color: inherit; // Correct color not being inherited. Known issue: affects color of disabled elements. + color: inherit; // Correct color not being inherited. Known issue affects color of disabled elements. font: inherit; // Correct font properties not being inherited. - margin: 0; // Address margins set differently in Firefox 4+, Safari, and Chrome. + margin: 0; // Address margins set differently in Firefox 4 and later, Safari, and Chrome. } // Address 'overflow' set to 'hidden' in IE 8/9/10/11. @@ -234,6 +233,7 @@ html input[type='button'], input[type='reset'], input[type='submit'] { -webkit-appearance: button; + appearance: button; cursor: pointer; } @@ -243,7 +243,7 @@ html input[disabled] { cursor: default; } -// Remove inner padding and border in Firefox 4+. +// Remove inner padding and border in Firefox 4 and later. button, input { &::-moz-focus-inner { @@ -252,7 +252,7 @@ input { } } -// Address Firefox 4+ setting 'line-height' on 'input' using '!important' in the UA stylesheet. +// Address Firefox 4 and later setting 'line-height' on 'input' using '!important' in the UA stylesheet. input { line-height: normal; } @@ -275,6 +275,7 @@ input[type='number'] { // Address 'appearance' set to 'searchfield' in Safari and Chrome. input[type='search'] { -webkit-appearance: textfield; + appearance: textfield; } // Remove inner padding and search cancel button in Safari and Chrome on OS X. @@ -283,6 +284,7 @@ input[type='search'] { &::-webkit-search-cancel-button, &::-webkit-search-decoration { -webkit-appearance: none; + appearance: none; } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less index a2e6a4485cc02..cd089232412dc 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less @@ -168,6 +168,9 @@ width: auto; z-index: 1; + /** + * @codingStandardsIgnoreStart + */ &:hover { &:extend(.abs-form-control-pattern:hover); } @@ -231,7 +234,6 @@ border: 0; display: inline; margin: 0; - width: 6rem; body._keyfocus &:focus { box-shadow: none; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-switcher.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-switcher.less index fbef3f4b5f3a6..cf7a8dd56cb3b 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-switcher.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-switcher.less @@ -130,7 +130,6 @@ display: block; height: @actions-switcher__height; transition: background @actions-switcher-speed ease-in 0s; - vertical-align: middle; width: @actions-switcher__width; z-index: 0; } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less index 96b8ce8df1219..88962a1019a19 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less @@ -26,6 +26,7 @@ border: none; opacity: 1; position: static; + transform: none; } } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less index 79e3975a41bc6..9a88d5e3593b9 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less @@ -74,6 +74,14 @@ box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; } } + + &:disabled { + + .file-uploader-button { + cursor: default; + opacity: .5; + pointer-events: none; + } + } } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_media-gallery.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_media-gallery.less index 85ccc235a3bdd..9a5f35e4ede90 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_media-gallery.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_media-gallery.less @@ -159,7 +159,7 @@ } .file-row { - background: @color-white url("@{baseDir}mui/images/ajax-loader-big.gif") no-repeat 50% 50%; // TODO UI: remove after new uploader implemented + background: @color-white url("@{baseDir}mui/images/ajax-loader-big.gif") no-repeat 50% 50%; bottom: 0; height: 100%; left: 0; @@ -324,6 +324,7 @@ -ms-flex: 1; -webkit-box-flex: 1; -webkit-flex: 1; + box-flex: 1; flex: 1; } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less index 7bd10ad491f0c..d1d1ff9891634 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less @@ -283,6 +283,7 @@ border: none; opacity: 1; position: static; + transform: none; } } @@ -462,25 +463,3 @@ } } } - -.ie9 { - .catalog-product-attribute-edit { - &.attribute-popup { - min-width: 0; - - .menu-wrapper { - display: none; - } - - .page-actions { - button { - float: none; - } - - .primary { - float: right; - } - } - } - } -} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_spinner.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_spinner.less index c9e56a29f275f..f6f61c1efae91 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_spinner.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_spinner.less @@ -33,14 +33,6 @@ position: absolute; width: 1em; } - - .ie9 & { - background: url('@{baseDir}images/ajax-loader.gif') no-repeat center; - - > span { - display: none; - } - } } // ToDo UI: remove old loaders style while loaders redesign diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less index 5f1cee13b5b88..bb51abaa0f156 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less @@ -100,26 +100,8 @@ &::-ms-expand { display: none; } - - .ie9 & { - background-image: none; - padding-right: @field-control__padding-horizontal; - } } -// ToDo UI: add month and date styles -// .admin__control-select-month { -// width: 140px; -// } - -// .admin__control-select-year { -// width: 103px; -// } - -// .admin__control-cvn { -// width: 3em; -// } - option:empty { display: none; } @@ -152,21 +134,24 @@ option:empty { &:before { &:extend(.abs-form-control-pattern); - content:''; - left: 0; - position: absolute; - top: 0; - width: 100%; - z-index: 0; - .admin__control-file:active + &, .admin__control-file:focus + & { + /** + * @codingStandardsIgnoreStart + */ &:extend(.abs-form-control-pattern:focus); } .admin__control-file[disabled] + & { &:extend(.abs-form-control-pattern[disabled]); } + + content: ''; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 0; } } @@ -336,35 +321,3 @@ option:empty { .admin__addon-prefix { .lib-vendor-prefix-order(0); } - -.ie9 { - .admin__control-addon { - &:after { - clear: both; - content: ''; - display: block; - height: 0; - overflow: hidden; - } - } - - .admin__addon { - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - width: auto; - - [class*='admin__control-'] { - display: inline; - } - - &-prefix { - float: left; - } - - &-suffix { - float: right; - } - } -} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 59675de698787..c87b8f2ca5cc0 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -183,10 +183,13 @@ .admin__field-label { color: @field-label__color; - cursor: pointer; margin: 0; text-align: right; + label { + cursor: pointer; + } + + br { display: none; } @@ -225,7 +228,7 @@ .required > &, // ToDo UI: change classes 'required' to '_required'. ._required > & { - > span { + span { &:after { color: @validation__color; content: '*'; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less index 0bfa454adbf0d..df031bebeb24a 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less @@ -408,6 +408,9 @@ label.mage-error { width: 16px; z-index: 1; + /** + * @codingStandardsIgnoreStart + */ &:before { &:extend(.admin__control-checkbox + label:before); left: 0; @@ -434,6 +437,7 @@ label.mage-error { &:before { &:extend(.admin__control-checkbox:checked + label:before); } + // @codingStandardsIgnoreEnd } &._indeterminate { @@ -470,6 +474,7 @@ label.mage-error { .action-select-multiselect { -webkit-appearance: menulist-button; + appearance: menulist-button; height: 38px; left: -1rem; min-width: 0; diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index b66fed0c0a09a..74098f127eb73 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -279,7 +279,6 @@ .nested .choice .label, .nested .choice .control { float: none; - width: auto; position: static; left: auto; text-align: left; @@ -487,8 +486,8 @@ } .notification-entry-dialog .action-close:hover { - color: #000; - border-bottom-color: #000; + color: @color-black; + border-bottom-color: @color-black; filter: none; } @@ -525,7 +524,7 @@ right: 0; top: 100%; border: 1px solid #cac2b5; - background: #fff; + background: @color-white; box-shadow: 0 1px 1px rgba(0, 0, 0, .2); z-index: 990; } @@ -786,7 +785,7 @@ border-radius: 1px; padding: 4px; color: #303030; - background-color: #fff; + background-color: @color-white; font-weight: 500; font-size: 14px; height: 33px; @@ -860,7 +859,7 @@ input[type="radio"], input[type="checkbox"] { .lib-css(appearance, none, 1); - background: #fff; + background: @color-white; border-radius: 2px; border: 1px solid #adadad; cursor: pointer; @@ -945,7 +944,7 @@ } select[disabled] option[selected] { - color: #fff; + color: @color-white; background: #aaa; } @@ -1157,7 +1156,7 @@ .fieldset-wrapper, .fieldset { - background: #fff; + background: @color-white; border: 0; margin: 0; padding: 5px 0 38px; @@ -1215,6 +1214,7 @@ -webkit-user-select: none; // use in 41 Chrome -moz-user-select: none; // use in 36 Firefox -ms-user-select: none; // use in 11 IE + user-select: none; min-height: 39px; } @@ -1574,7 +1574,7 @@ .multiselect-alt .item { position: relative; - border-top: 1px solid #fff; + border-top: 1px solid @color-white; cursor: pointer; } @@ -1651,7 +1651,7 @@ ~ .addafter strong { display: inline-block; - background: #fff; + background: @color-white; line-height: 24px; margin: 0 3px 0 0; padding-left: 4px; @@ -2041,7 +2041,7 @@ &:before { position: absolute; content: ""; - background-color: #fff; + background-color: @color-white; right: 0; top: 0; bottom: 0; @@ -2298,7 +2298,7 @@ // -------------------------------------- .preview-window { - background: #fff; + background: @color-white; } .preview-window .toolbar { @@ -2458,7 +2458,7 @@ } .attribute-popup .page-actions.fixed .page-actions-inner { - background: #fff; + background: @color-white; padding: 0; min-width: 100%; max-width: 100%; @@ -2757,7 +2757,7 @@ border: 1px solid #c9c2b8; border-width: 0 0 1px; padding: 6px 10px 7px; - background: #fff; + background: @color-white; .style2(); span { @@ -2769,7 +2769,7 @@ td { border: none; padding: 6px 10px 7px; - background: #fff; + background: @color-white; } tr:last-child td { @@ -2858,7 +2858,7 @@ .table-fieldset-alt tbody tr:nth-child(odd) td, .table-fieldset-alt tbody tr:nth-child(odd):hover td { - background: #fff; + background: @color-white; } // @@ -2918,7 +2918,7 @@ } .order-details-existing-customer { - background: #fff; + background: @color-white; padding-left: 0; position: relative; width: 77.9%; @@ -3035,7 +3035,7 @@ } .gift-options-tooltip { - background: #fff; + background: @color-white; border-radius: 5px; padding: 10px; box-shadow: 0 0 3px rgba(0, 0, 0, .3); @@ -3194,7 +3194,7 @@ left: 0; margin: 0 8px; padding: 10px; - background: #fff; + background: @color-white; } } @@ -3386,7 +3386,7 @@ .rma-popup, .cms-popup { - background: #fff; + background: @color-white; box-shadow: 0 3px 6px rgba(0, 0, 0, .4); cursor: default; position: fixed; @@ -3410,7 +3410,7 @@ } .rma-popup .content { - background: #fff; + background: @color-white; border-bottom: 1px solid #ccc; max-height: 400px; overflow: auto; @@ -3467,7 +3467,7 @@ .grid .rma-popup .form-list tr, .grid tr.even .rma-popup .form-list tr, .grid tr.on-mouse .rma-popup .form-list tr { - background: #fff !important; + background: @color-white !important; } // @@ -3798,7 +3798,7 @@ } .rule-param .label { - color: #000; + color: @color-black; float: none; text-align: left; padding: 0; @@ -3885,7 +3885,7 @@ .defaultSkin { table.mceLayout { td { - background: #fff; + background: @color-white; } } @@ -3905,117 +3905,6 @@ } } -// -// IE9 styles -// --------------------------------------------- - -.ie9 { - .admin__scope-old { - select { - &:not([multiple]) { - padding-right: 4px; - min-width: 0; - } - } - - // Table Filters - .filter select { - &:not([multiple]) { - padding-right: 0; - } - } - - .adminhtml-widget-instance-edit { - .grid-chooser .control { - margin-top: -18px; - } - } - .page-layout-admin-1column .page-columns, - .catalog-product-edit, - .catalog-product-new, - .catalog-category-edit { - table.data { - table-layout: fixed; - word-wrap: break-word; - - th { - word-wrap: normal; - overflow: hidden; - vertical-align: top; - - > span { - white-space: normal; - } - } - - th:not(.col-select):not(.col-id):not(.col-severity), - td:not(.col-select):not(.col-id):not(.col-severity) { - width: auto; - } - } - } - - #setGrid_table, - #attributeGrid_table, - .custom-options .data-table, - .ui-dialog .data, - .page-layout-admin-1column .page-columns .data, - .catalog-category-edit .data { - word-wrap: break-word; - table-layout: fixed; - } - - .fieldset-wrapper { - table.data { - table-layout: inherit; - word-wrap: normal; - } - } - - .sales-order-create-index table.data, - .sales-order-create-index .fieldset-wrapper table.data { - table-layout: fixed; - word-wrap: break-word; - - th { - word-wrap: normal; - overflow: hidden; - vertical-align: top; - - > span { - white-space: normal; - } - } - } - - .entry-edit .product-options .grouped-items-table { - table-layout: fixed; - word-wrap: break-word; - - th { - word-wrap: normal; - overflow: hidden; - vertical-align: top; - - > span { - white-space: normal; - } - } - } - - .catalog-category-edit, - .adminhtml-cache-index, - .adminhtml-process-list, - .indexer-indexer-list, - .adminhtml-notification-index { - table.data { - table-layout: inherit; - word-wrap: normal; - } - } - } -} - // // Pages styles // --------------------------------------------- @@ -5229,7 +5118,7 @@ @media print { * { background: transparent !important; - color: #000 !important; + color: @color-black !important; box-shadow: none !important; text-shadow: none !important; filter: none !important; diff --git a/app/design/adminhtml/Magento/backend/web/mui/styles/_table.less b/app/design/adminhtml/Magento/backend/web/mui/styles/_table.less index c0aa5510c6e07..70ae82045b3d1 100644 --- a/app/design/adminhtml/Magento/backend/web/mui/styles/_table.less +++ b/app/design/adminhtml/Magento/backend/web/mui/styles/_table.less @@ -204,16 +204,6 @@ } } -.eq-ie9 { - .hor-scroll { - display: inline-block; - min-height: 0; - overflow-y: hidden; - overflow-x: auto; - width: 100%; - } -} - td.col-period, td.col-date, td.col-date_to, @@ -360,7 +350,7 @@ td.col-type { position: relative; border-bottom-right-radius: 0; box-shadow: none; - background: #fff; + background: @color-white; &:after { position: absolute; @@ -369,7 +359,7 @@ td.col-type { right: 0; height: 2px; margin-top: -1px; - background: #fff; + background: @color-white; content: ''; z-index: 2; } @@ -455,7 +445,6 @@ td.col-type { .import { display: block; - vertical-align: top; } .action-reset { diff --git a/app/design/frontend/Magento/blank/Magento_CatalogSearch/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_CatalogSearch/web/css/source/_module.less index 52f1beafa32e1..daed96db717c7 100644 --- a/app/design/frontend/Magento/blank/Magento_CatalogSearch/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_CatalogSearch/web/css/source/_module.less @@ -36,9 +36,9 @@ @_icon-font-content: @icon-search, @_icon-font-size: 35px, @_icon-font-line-height: 33px, - @_icon-font-color: @minicart-icons-color, - @_icon-font-color-hover: @minicart-icons-color-hover, - @_icon-font-color-active: @minicart-icons-color-hover, + @_icon-font-color: @header-icons-color, + @_icon-font-color-hover: @header-icons-color-hover, + @_icon-font-color-active: @header-icons-color-hover, @_icon-font-text-hide: true ); display: inline-block; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less index bb14a3c2521b0..d3d15019f0e87 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less @@ -305,7 +305,7 @@ white-space: nowrap; width: 33%; - &:before { + &[data-th]:before { content: attr(data-th) ':'; display: block; font-weight: @font-weight__bold; diff --git a/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less index 6baa2432ff035..145dc10263fb1 100644 --- a/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less @@ -35,12 +35,19 @@ .items { text-align: left; .item { + > span { + display: block; + padding: 5px 5px 5px 23px; + } &:last-child { &:hover { .lib-css(background, @dropdown-list-item__hover); } } } + li { + padding: 0; + } } .table-comparison &, diff --git a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less index 66dbb30cb6712..96648f504af80 100644 --- a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less @@ -113,14 +113,6 @@ } } } - - .cart-price { - &:extend(.abs-checkout-cart-price all); - } - - .product-item-name { - &:extend(.abs-checkout-product-name all); - } } &:not(.address) { diff --git a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less index 84adec39c8892..215d7d8b322b4 100644 --- a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less +++ b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_email.less @@ -203,7 +203,8 @@ text-align: center; } - .item-price { + .item-price, + .item-subtotal { text-align: right; } diff --git a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less index ed7af5872483b..8df5556e97721 100644 --- a/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Theme/web/css/source/_module.less @@ -52,6 +52,16 @@ .lib-css(background-color, @page__background-color); } + .page-wrapper { + .lib-vendor-prefix-display(flex); + .lib-vendor-prefix-flex-direction(column); + min-height: 100vh; // Stretch content area for sticky footer + } + + .page-main { + .lib-vendor-prefix-flex-grow(1); + } + // // Header // --------------------------------------------- @@ -279,17 +289,7 @@ // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { - - html, - body { - height: 100%; // Stretch screen area for sticky footer - } - .page-wrapper { - .lib-vendor-prefix-display(flex); - .lib-vendor-prefix-flex-direction(column); - min-height: 100%; // Stretch content area for sticky footer - > .breadcrumbs, > .top-container, > .widget { @@ -297,7 +297,6 @@ width: 100%; } - .ie10 &, .ie11 & { height: 100%; } diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index c177f91e9e7e8..13da4a4d996cc 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -803,20 +803,6 @@ } } -// -// Checkout order review price -// --------------------------------------------- - -.abs-checkout-cart-price { -} - -// -// Checkout order product name -// --------------------------------------------- - -.abs-checkout-product-name { -} - // // Checkout order review // --------------------------------------------- @@ -846,7 +832,7 @@ white-space: nowrap; width: 33%; - &:before { + &[data-th]:before { content: attr(data-th) ':'; display: block; font-weight: @font-weight__bold; diff --git a/app/design/frontend/Magento/blank/web/css/source/_layout.less b/app/design/frontend/Magento/blank/web/css/source/_layout.less index 8d01ddc0a9bd3..24743c665b460 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_layout.less +++ b/app/design/frontend/Magento/blank/web/css/source/_layout.less @@ -94,14 +94,6 @@ .page-main { width: 100%; - - .lib-vendor-prefix-flex-grow(1); - .lib-vendor-prefix-flex-shrink(0); - .lib-vendor-prefix-flex-basis(auto); - - .ie9 & { - width: auto; - } } .columns { diff --git a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less index 23acf9ee8b08d..f785dd74d900e 100644 --- a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less @@ -37,9 +37,9 @@ @_icon-font-size: 22px, @_icon-font-line-height: 28px, @_icon-font-margin: 0 @indent__s 0 0, - @_icon-font-color: @minicart-icons-color, - @_icon-font-color-hover: @minicart-icons-color-hover, - @_icon-font-color-active: @minicart-icons-color-hover, + @_icon-font-color: @header-icons-color, + @_icon-font-color-hover: @header-icons-color-hover, + @_icon-font-color-active: @header-icons-color-hover, @_icon-font-text-hide: true ); display: inline-block; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index 43b0351f0ff77..9e3a28be4c90e 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -460,7 +460,7 @@ white-space: nowrap; width: 33%; - &:before { + &[data-th]:before { content: attr(data-th); display: block; font-weight: @font-weight__semibold; diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less old mode 100644 new mode 100755 index 832082aa6557a..d7ae6c3b28f4a --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -93,6 +93,21 @@ } } } + .fieldset.create.account { + .lib-form-hasrequired(bottom); + &:after { + margin-top: 35px; + } + } + } + + .form.password.forget { + .fieldset { + .lib-form-hasrequired(bottom); + &:after { + margin-top: 35px; + } + } } // Full name fieldset @@ -209,7 +224,7 @@ .column.main & { } } - + display: block; margin-bottom: @indent__s; } diff --git a/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less index da284eab8f49e..6ab7a8e47a174 100644 --- a/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less @@ -40,6 +40,16 @@ .items { padding: 6px 0; text-align: left; + .item { + > span { + display: block; + padding: 5px 5px 5px 23px; + } + + } + li { + padding: 0; + } } > .action { diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less index 9a3f433618c36..3f19d1020bab9 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_email.less @@ -207,7 +207,8 @@ text-align: center; } - .item-price { + .item-price, + .item-subtotal { text-align: right; } diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less index 692f91ef463b1..a0f734b05cbd1 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less @@ -482,7 +482,7 @@ .options-label + .item-options-container, .item-options-container + .item-options-container { - &:before { + &[data-th]:before { content: attr(data-th) ':'; display: block; font-weight: @font-weight__bold; diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less index 83157281561c2..bafe1be57ee35 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less @@ -62,18 +62,21 @@ // Common // _____________________________________________ -.page-wrapper { - .ie9 & { - .lib-css(background-color, @page__background-color); - min-height: 0; - } -} - & when (@media-common = true) { body { .lib-css(background-color, @page__background-color); } + .page-wrapper { + .lib-vendor-prefix-display(flex); + .lib-vendor-prefix-flex-direction(column); + min-height: 100vh; // Stretch content area for sticky footer + } + + .page-main { + .lib-vendor-prefix-flex-grow(1); + } + // // Header // --------------------------------------------- @@ -261,6 +264,7 @@ .copyright { .lib-css(background-color, @copyright__background-color); .lib-css(color, @color-white); + box-sizing: border-box; display: block; padding: @indent__s; text-align: center; @@ -312,7 +316,6 @@ // // Widgets // --------------------------------------------- - .sidebar { .widget.block:not(:last-child), .widget:not(:last-child) { @@ -426,6 +429,17 @@ } } +// +// Mobile +// _____________________________________________ + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .cms-page-view .page-main { + padding-top: 41px; + position: relative; + } +} + // // Desktop // _____________________________________________ @@ -436,12 +450,6 @@ height: 100%; // Stretch screen area for sticky footer } - body { - .ie9 & { - .lib-css(background-color, @copyright__background-color); - } - } - .navigation ul { padding: 0 8px; } @@ -609,10 +617,7 @@ } .page-wrapper { - .lib-vendor-prefix-display(flex); - .lib-vendor-prefix-flex-direction(column); margin: 0; - min-height: 100%; // Stretch content area for sticky footer position: relative; transition: margin .3s ease-out 0s; @@ -623,7 +628,6 @@ width: 100%; } - .ie10 &, .ie11 & { height: 100%; } diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index 41d565789c25b..55d43272caad9 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -232,6 +232,7 @@ <var name="height"></var> <!-- Height of magnifier block --> <var name="eventType">hover</var> <!-- Action that atcivates zoom (hover/click) --> <var name="enabled">false</var> <!-- Turn on/off magnifier (true/false) --> + <var name="mode">outside</var> <!-- Zoom type (outside/inside) --> </var> <var name="breakpoints"> diff --git a/app/design/frontend/Magento/luma/web/css/source/_extends.less b/app/design/frontend/Magento/luma/web/css/source/_extends.less index 760ec9ed861a9..7c9f5b7a65ab4 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -7,6 +7,8 @@ // List default styles reset // --------------------------------------------- +@_column-number: 1; + & when (@media-common = true) { .abs-reset-list { .lib-list-reset-styles(); @@ -705,7 +707,7 @@ // --------------------------------------------- @abs-form-field-revert-column-1: { - .lib-form-field-column-number(@_column-number: 1); + .lib-form-field-column-number(@_column-number); }; .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { @@ -760,7 +762,7 @@ white-space: nowrap; width: 33%; - &:before { + &[data-th]:before { content: attr(data-th) ':'; display: block; font-weight: @font-weight__bold; diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less index 7c5027aef113b..0c7150c18550b 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_forms.less +++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less @@ -98,11 +98,6 @@ &::-ms-expand { display: none; } - - .lt-ie10 & { - background-image: none; - padding-right: 4px; - } } select { diff --git a/app/etc/di.xml b/app/etc/di.xml index 1f5507deb0519..5bc25e6cb85f7 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -204,6 +204,9 @@ <preference for="Magento\Framework\MessageQueue\ExchangeFactoryInterface" type="Magento\Framework\MessageQueue\ExchangeFactory" /> <preference for="Magento\Framework\MessageQueue\Bulk\ExchangeFactoryInterface" type="Magento\Framework\MessageQueue\Bulk\ExchangeFactory" /> <preference for="Magento\Framework\MessageQueue\QueueFactoryInterface" type="Magento\Framework\MessageQueue\QueueFactory" /> + <preference for="Magento\Framework\App\Request\ValidatorInterface" + type="Magento\Framework\App\Request\CsrfValidator" /> + <preference for="Magento\Framework\Search\Request\IndexScopeResolverInterface" type="Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver"/> <type name="Magento\Framework\Model\ResourceModel\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Acl\Data\Cache"> <arguments> diff --git a/composer.json b/composer.json index f8182c96f78a0..64d4bcf3b4feb 100644 --- a/composer.json +++ b/composer.json @@ -38,9 +38,10 @@ "elasticsearch/elasticsearch": "~2.0|~5.1", "magento/composer": "~1.4.0", "magento/magento-composer-installer": ">=0.1.11", - "magento/zendframework1": "~1.14.0", + "magento/zendframework1": "~1.14.1", "monolog/monolog": "^1.17", "oyejorge/less.php": "~1.7.0", + "paragonie/sodium_compat": "^1.6", "pelago/emogrifier": "^2.0.0", "php-amqplib/php-amqplib": "~2.7.0", "phpseclib/mcrypt_compat": "1.0.5", @@ -81,6 +82,7 @@ "zendframework/zend-view": "~2.10.0" }, "require-dev": { + "magento/magento2-functional-testing-framework": "2.3.5", "friendsofphp/php-cs-fixer": "~2.12.0", "lusitanian/oauth": "~0.8.10", "pdepend/pdepend": "2.5.2", @@ -89,6 +91,9 @@ "sebastian/phpcpd": "~3.0.0", "squizlabs/php_codesniffer": "3.3.0" }, + "suggest": { + "ext-pcntl": "Need for run processes in parallel mode" + }, "replace": { "magento/module-marketplace": "*", "magento/module-admin-notification": "*", @@ -251,6 +256,9 @@ "tinymce/tinymce": "3.4.7", "magento/module-tinymce-3": "*" }, + "conflict": { + "gene/bluefoot": "*" + }, "extra": { "component_paths": { "trentrichardson/jquery-timepicker-addon": "lib/web/jquery/jquery-ui-timepicker-addon.js", @@ -278,7 +286,8 @@ }, "psr-0": { "": [ - "app/code/" + "app/code/", + "generated/code/" ] }, "files": [ diff --git a/composer.lock b/composer.lock index 0966aee19489e..7af89dd62065b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c6ae2be0f066e566e8280b2954aad257", + "content-hash": "84697de5cc19e39e480943f1333aef0a", "packages": [ { "name": "braintree/braintree_php", @@ -201,16 +201,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", "shasum": "" }, "require": { @@ -253,30 +253,30 @@ "ssl", "tls" ], - "time": "2018-03-29T19:57:20+00:00" + "time": "2018-08-08T08:57:40+00:00" }, { "name": "composer/composer", - "version": "1.6.5", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b184a92419cc9a9c4c6a09db555a94d441cb11c9" + "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b184a92419cc9a9c4c6a09db555a94d441cb11c9", - "reference": "b184a92419cc9a9c4c6a09db555a94d441cb11c9", + "url": "https://api.github.com/repos/composer/composer/zipball/576aab9b5abb2ed11a1c52353a759363216a4ad2", + "reference": "576aab9b5abb2ed11a1c52353a759363216a4ad2", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0", "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.1", "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", "php": "^5.3.2 || ^7.0", "psr/log": "^1.0", - "seld/cli-prompt": "^1.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.0", "symfony/console": "^2.7 || ^3.0 || ^4.0", @@ -302,7 +302,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -333,7 +333,7 @@ "dependency", "package" ], - "time": "2018-05-04T09:44:59+00:00" + "time": "2018-08-16T14:57:12+00:00" }, { "name": "composer/semver", @@ -458,6 +458,50 @@ ], "time": "2018-04-30T10:33:04+00:00" }, + { + "name": "composer/xdebug-handler", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e37cbd80da64afe314c72de8d2d2fec0e40d9373", + "reference": "e37cbd80da64afe314c72de8d2d2fec0e40d9373", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2018-08-23T12:00:19+00:00" + }, { "name": "container-interop/container-interop", "version": "1.2.0", @@ -546,16 +590,16 @@ }, { "name": "guzzlehttp/ringphp", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/guzzle/RingPHP.git", - "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b" + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", - "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", "shasum": "" }, "require": { @@ -593,7 +637,7 @@ } ], "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "time": "2015-05-20T03:37:09+00:00" + "time": "2018-07-31T13:22:33+00:00" }, { "name": "guzzlehttp/streams", @@ -828,25 +872,18 @@ }, { "name": "magento/zendframework1", - "version": "1.14.0", + "version": "1.14.1", "source": { "type": "git", "url": "https://github.com/magento/zf1.git", - "reference": "68522e5768edc8e829d1f64b620a3de3753f1141" + "reference": "4df018254c70b5b998b00a8cb1a30760f831ff0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/68522e5768edc8e829d1f64b620a3de3753f1141", - "reference": "68522e5768edc8e829d1f64b620a3de3753f1141", + "url": "https://api.github.com/repos/magento/zf1/zipball/4df018254c70b5b998b00a8cb1a30760f831ff0d", + "reference": "4df018254c70b5b998b00a8cb1a30760f831ff0d", "shasum": "" }, - "archive": { - "exclude": [ - "/demos", - "/documentation", - "/tests" - ] - }, "require": { "php": ">=5.2.11" }, @@ -865,6 +902,7 @@ "Zend_": "library/" } }, + "notification-url": "https://packagist.org/downloads/", "include-path": [ "library/" ], @@ -874,14 +912,10 @@ "description": "Magento Zend Framework 1", "homepage": "http://framework.zend.com/", "keywords": [ - "framework", - "zf1" + "ZF1", + "framework" ], - "support": { - "source": "https://github.com/magento-engcom/zf1-php-7.2-support/tree/master", - "issues": "https://github.com/magento-engcom/zf1-php-7.2-support/issues" - }, - "time": "2018-04-06T17:12:22+00:00" + "time": "2018-08-09T15:03:40+00:00" }, { "name": "monolog/monolog", @@ -1025,16 +1059,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.15", + "version": "v2.0.17", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" + "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d", + "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d", "shasum": "" }, "require": { @@ -1070,7 +1104,89 @@ "pseudorandom", "random" ], - "time": "2018-06-08T15:26:40+00:00" + "time": "2018-07-04T16:31:37+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "7d0549c3947eaea620f4e523f42ab236cf7fd304" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/7d0549c3947eaea620f4e523f42ab236cf7fd304", + "reference": "7d0549c3947eaea620f4e523f42ab236cf7fd304", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "time": "2018-06-06T17:30:29+00:00" }, { "name": "pelago/emogrifier", @@ -1625,54 +1741,6 @@ ], "time": "2018-06-13T15:59:06+00:00" }, - { - "name": "seld/cli-prompt", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/cli-prompt.git", - "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/cli-prompt/zipball/a19a7376a4689d4d94cab66ab4f3c816019ba8dd", - "reference": "a19a7376a4689d4d94cab66ab4f3c816019ba8dd", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Seld\\CliPrompt\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Allows you to prompt for user input on the command line, and optionally hide the characters they type", - "keywords": [ - "cli", - "console", - "hidden", - "input", - "prompt" - ], - "time": "2017-03-18T11:32:45+00:00" - }, { "name": "seld/jsonlint", "version": "1.7.1", @@ -1768,16 +1836,16 @@ }, { "name": "symfony/console", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f" + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/70591cda56b4b47c55776ac78e157c4bb6c8b43f", - "reference": "70591cda56b4b47c55776ac78e157c4bb6c8b43f", + "url": "https://api.github.com/repos/symfony/console/zipball/ca80b8ced97cf07390078b29773dc384c39eee1f", + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f", "shasum": "" }, "require": { @@ -1832,20 +1900,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-31T10:17:53+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5" + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/2391ed210a239868e7256eb6921b1bd83f3087b5", - "reference": "2391ed210a239868e7256eb6921b1bd83f3087b5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bfb30c2ad377615a463ebbc875eba64a99f6aa3e", + "reference": "bfb30c2ad377615a463ebbc875eba64a99f6aa3e", "shasum": "" }, "require": { @@ -1895,20 +1963,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:57+00:00" + "time": "2018-07-26T09:10:45+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" + "reference": "c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e", + "reference": "c0f5f62db218fa72195b8b8700e4b9b9cf52eb5e", "shasum": "" }, "require": { @@ -1945,20 +2013,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2018-08-18T16:52:46+00:00" }, { "name": "symfony/finder", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" + "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", + "url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068", + "reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068", "shasum": "" }, "require": { @@ -1994,29 +2062,32 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-06-19T21:38:16+00:00" + "time": "2018-07-26T11:24:31+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -2049,20 +2120,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", "shasum": "" }, "require": { @@ -2074,7 +2145,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -2108,20 +2179,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/process", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" + "reference": "86cdb930a6a855b0ab35fb60c1504cb36184f843" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "url": "https://api.github.com/repos/symfony/process/zipball/86cdb930a6a855b0ab35fb60c1504cb36184f843", + "reference": "86cdb930a6a855b0ab35fb60c1504cb36184f843", "shasum": "" }, "require": { @@ -2157,7 +2228,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-31T10:17:53+00:00" + "time": "2018-08-03T11:13:38+00:00" }, { "name": "tedivm/jshrink", @@ -2411,16 +2482,16 @@ }, { "name": "zendframework/zend-code", - "version": "3.3.0", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-code.git", - "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d" + "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/6b1059db5b368db769e4392c6cb6cc139e56640d", - "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", + "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", "shasum": "" }, "require": { @@ -2441,8 +2512,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" + "dev-master": "3.3.x-dev", + "dev-develop": "3.4.x-dev" } }, "autoload": { @@ -2460,7 +2531,7 @@ "code", "zf2" ], - "time": "2017-10-20T15:21:32+00:00" + "time": "2018-08-13T20:36:59+00:00" }, { "name": "zendframework/zend-config", @@ -2728,16 +2799,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "1.8.0", + "version": "1.8.5", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e" + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/11c9c1835e60eef6f9234377a480fcec096ebd9e", - "reference": "11c9c1835e60eef6f9234377a480fcec096ebd9e", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/3e4edb822c942f37ade0d09579cfbab11e2fee87", + "reference": "3e4edb822c942f37ade0d09579cfbab11e2fee87", "shasum": "" }, "require": { @@ -2750,7 +2821,7 @@ "require-dev": { "ext-dom": "*", "ext-libxml": "*", - "phpunit/phpunit": "^5.7.16 || ^6.0.8", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", "zendframework/zend-coding-standard": "~1.0" }, "type": "library", @@ -2787,7 +2858,7 @@ "psr", "psr-7" ], - "time": "2018-06-27T18:52:43+00:00" + "time": "2018-08-10T14:16:32+00:00" }, { "name": "zendframework/zend-escaper", @@ -2883,16 +2954,16 @@ }, { "name": "zendframework/zend-feed", - "version": "2.10.2", + "version": "2.10.3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-feed.git", - "reference": "5253f949f4ad999086ab9b408908b6c6776f24db" + "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/5253f949f4ad999086ab9b408908b6c6776f24db", - "reference": "5253f949f4ad999086ab9b408908b6c6776f24db", + "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", + "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888", "shasum": "" }, "require": { @@ -2940,7 +3011,7 @@ "feed", "zf" ], - "time": "2018-06-18T20:14:01+00:00" + "time": "2018-08-01T13:53:20+00:00" }, { "name": "zendframework/zend-filter", @@ -3085,16 +3156,16 @@ }, { "name": "zendframework/zend-http", - "version": "2.8.0", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51" + "reference": "2c8aed3d25522618573194e7cc51351f8cd4a45b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51", - "reference": "f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/2c8aed3d25522618573194e7cc51351f8cd4a45b", + "reference": "2c8aed3d25522618573194e7cc51351f8cd4a45b", "shasum": "" }, "require": { @@ -3136,7 +3207,7 @@ "zend", "zf" ], - "time": "2018-04-26T21:04:50+00:00" + "time": "2018-08-13T18:47:03+00:00" }, { "name": "zendframework/zend-hydrator", @@ -4446,150 +4517,142 @@ ], "packages-dev": [ { - "name": "composer/xdebug-handler", - "version": "1.1.0", + "name": "allure-framework/allure-codeception", + "version": "1.2.7", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" + "url": "https://github.com/allure-framework/allure-codeception.git", + "reference": "48598f4b4603b50b663bfe977260113a40912131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", + "reference": "48598f4b4603b50b663bfe977260113a40912131", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", - "psr/log": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "allure-framework/allure-php-api": "~1.1.0", + "codeception/codeception": "~2.1", + "php": ">=5.4.0", + "symfony/filesystem": ">=2.6", + "symfony/finder": ">=2.6" }, "type": "library", "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" + "psr-0": { + "Yandex": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" } ], - "description": "Restarts a process without xdebug.", + "description": "A Codeception adapter for Allure report.", + "homepage": "http://allure.qatools.ru/", "keywords": [ - "Xdebug", - "performance" - ], - "time": "2018-04-11T15:42:36+00:00" + "allure", + "attachments", + "cases", + "codeception", + "report", + "steps", + "testing" + ], + "time": "2018-03-07T11:18:27+00:00" }, { - "name": "doctrine/annotations", - "version": "v1.6.0", + "name": "allure-framework/allure-php-api", + "version": "1.1.4", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "url": "https://github.com/allure-framework/allure-php-adapter-api.git", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "jms/serializer": ">=0.16.0", + "moontoast/math": ">=1.1.0", + "php": ">=5.4.0", + "phpunit/phpunit": ">=4.0.0", + "ramsey/uuid": ">=3.0.0", + "symfony/http-foundation": ">=2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "psr-0": { + "Yandex": [ + "src/", + "test/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "description": "PHP API for Allure adapter", + "homepage": "http://allure.qatools.ru/", "keywords": [ - "annotations", - "docblock", - "parser" + "allure", + "api", + "php", + "report" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2016-12-07T12:15:46+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.1.0", + "name": "behat/gherkin", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", "shasum": "" }, "require": { - "php": "^7.1" + "php": ">=5.3.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "4.4-dev" } }, "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "psr-0": { + "Behat\\Gherkin": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4598,146 +4661,1534 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", "keywords": [ - "constructor", - "instantiate" + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2016-10-30T11:50:56+00:00" }, { - "name": "doctrine/lexer", - "version": "v1.0.1", + "name": "codeception/codeception", + "version": "2.3.9", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "url": "https://github.com/Codeception/Codeception.git", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", "shasum": "" }, "require": { - "php": ">=5.3.2" + "behat/gherkin": "~4.4.0", + "codeception/stub": "^1.0", + "ext-json": "*", + "ext-mbstring": "*", + "facebook/webdriver": ">=1.1.3 <2.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/psr7": "~1.0", + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": ">=1.4 <3.0", + "symfony/browser-kit": ">=2.7 <5.0", + "symfony/console": ">=2.7 <5.0", + "symfony/css-selector": ">=2.7 <5.0", + "symfony/dom-crawler": ">=2.7 <5.0", + "symfony/event-dispatcher": ">=2.7 <5.0", + "symfony/finder": ">=2.7 <5.0", + "symfony/yaml": ">=2.7 <5.0" + }, + "require-dev": { + "codeception/specify": "~0.3", + "facebook/graph-sdk": "~5.3", + "flow/jsonpath": "~0.2", + "monolog/monolog": "~1.8", + "pda/pheanstalk": "~3.0", + "php-amqplib/php-amqplib": "~2.4", + "predis/predis": "^1.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <5.0", + "vlucas/phpdotenv": "^2.4.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "flow/jsonpath": "For using JSONPath in REST module", + "league/factory-muffin": "For DataFactory module", + "league/factory-muffin-faker": "For Faker support in DataFactory module", + "phpseclib/phpseclib": "for SFTP option in FTP Module", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" }, + "bin": [ + "codecept" + ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Codeception\\": "src\\Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "time": "2018-02-26T23:29:41+00:00" + }, + { + "name": "codeception/stub", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", + "shasum": "" + }, + "require": { + "phpunit/phpunit-mock-objects": ">2.3 <7.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2018-05-17T09:31:08+00:00" + }, + { + "name": "consolidation/annotated-command", + "version": "2.8.5", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/1e8ff512072422b850b44aa721b5b303e4a5ebb3", + "reference": "1e8ff512072422b850b44aa721b5b303e4a5ebb3", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^3.1.12", + "php": ">=5.4.0", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^6", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "time": "2018-08-18T23:51:49+00:00" + }, + { + "name": "consolidation/config", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "c9fc25e9088a708637e18a256321addc0670e578" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/c9fc25e9088a708637e18a256321addc0670e578", + "reference": "c9fc25e9088a708637e18a256321addc0670e578", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "grasmash/expander": "^1", + "php": ">=5.4.0" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "^5", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "2.*", + "symfony/console": "^2.5|^3|^4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "suggest": { + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "time": "2018-08-07T22:57:00+00:00" + }, + { + "name": "consolidation/log", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/log": "~1.0", + "symfony/console": "^2.8|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "time": "2018-05-25T18:14:39+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^5.7.27", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7", + "symfony/console": "3.2.3", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2018-05-25T18:02:34+00:00" + }, + { + "name": "consolidation/robo", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/consolidation/Robo.git", + "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", + "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^2.8.2", + "consolidation/config": "^1.0.10", + "consolidation/log": "~1", + "consolidation/output-formatters": "^3.1.13", + "consolidation/self-update": "^1", + "g1a/composer-test-scenarios": "^2", + "grasmash/yaml-expander": "^1.3", + "league/container": "^2.2", + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/filesystem": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4", + "symfony/process": "^2.5|^3|^4" + }, + "replace": { + "codegyre/robo": "< 1.0" + }, + "require-dev": { + "codeception/aspect-mock": "^1|^2.1.1", + "codeception/base": "^2.3.7", + "codeception/verify": "^0.3.2", + "goaop/framework": "~2.1.2", + "goaop/parser-reflection": "^1.1.0", + "natxet/cssmin": "3.0.4", + "nikic/php-parser": "^3.1.5", + "patchwork/jsqueeze": "~2", + "pear/archive_tar": "^1.4.2", + "phpunit/php-code-coverage": "~2|~4", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.8" + }, + "suggest": { + "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", + "natxet/CssMin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." + }, + "bin": [ + "robo" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev", + "dev-state": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Robo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "Modern task runner", + "time": "2018-08-17T18:44:18+00:00" + }, + { + "name": "consolidation/self-update", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/self-update.git", + "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/filesystem": "^2.5|^3|^4" + }, + "bin": [ + "scripts/release" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "SelfUpdate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Alexander Menk", + "email": "menk@mestrona.net" + } + ], + "description": "Provides a self:update command for Symfony Console applications.", + "time": "2018-08-24T17:01:46+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^7.1" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-12-06T07:11:42+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-07-22T10:37:32+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "^6.2.3", + "squizlabs/php_codesniffer": "^3.0.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2017-07-22T11:58:36+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "epfremme/swagger-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/epfremmer/swagger-php.git", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2", + "doctrine/collections": "^1.3", + "jms/serializer": "^1.1", + "php": ">=5.5", + "phpoption/phpoption": "^1.1", + "symfony/yaml": "^2.7|^3.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "~4.8|~5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "package", + "autoload": { + "psr-4": { + "Epfremme\\Swagger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edward Pfremmer", + "email": "epfremme@nerdery.com" + } + ], + "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", + "time": "2016-09-26T17:24:17+00:00" + }, + { + "name": "facebook/webdriver", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "php-coveralls/php-coveralls": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^2.6", + "symfony/var-dumper": "^3.3 || ^4.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2018-05-16T17:37:13+00:00" + }, + { + "name": "flow/jsonpath", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/FlowCommunications/JSONPath.git", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "peekmo/jsonpath": "dev-master", + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Flow\\JSONPath": "src/", + "Flow\\JSONPath\\Test": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "time": "2018-03-04T16:39:47+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.12.3", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "b23d49981cfc95497d03081aeb6df6575196a0d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b23d49981cfc95497d03081aeb6df6575196a0d3", + "reference": "b23d49981cfc95497d03081aeb6df6575196a0d3", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.2", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.6 || >=7.0 <7.3", + "php-cs-fixer/diff": "^1.3", + "symfony/console": "^3.2 || ^4.0", + "symfony/event-dispatcher": "^3.0 || ^4.0", + "symfony/filesystem": "^3.0 || ^4.0", + "symfony/finder": "^3.0 || ^4.0", + "symfony/options-resolver": "^3.0 || ^4.0", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^3.0 || ^4.0", + "symfony/stopwatch": "^3.0 || ^4.0" + }, + "conflict": { + "hhvm": "*" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.1", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.1", + "php-cs-fixer/accessible-object": "^1.0", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", + "phpunitgoodpractices/traits": "^1.5.1", + "symfony/phpunit-bridge": "^4.0" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/Assert/AssertTokensTrait.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/TestCase.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2018-08-19T22:33:38+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "a166fd15191aceab89f30c097e694b7cf3db4880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880", + "reference": "a166fd15191aceab89f30c097e694b7cf3db4880", + "shasum": "" + }, + "bin": [ + "scripts/create-scenario", + "scripts/dependency-licenses", + "scripts/install-scenario" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2018-08-08T23:37:23+00:00" + }, + { + "name": "grasmash/expander", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "time": "2017-12-21T22:14:55+00:00" + }, + { + "name": "grasmash/yaml-expander", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-expander.git", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlExpander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in a yaml file.", + "time": "2017-12-16T16:06:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "jms/metadata", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "doctrine/cache": "~1.0", + "symfony/cache": "~3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } }, "autoload": { "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "Metadata\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", + "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "Class/method/property metadata management in PHP", "keywords": [ - "lexer", - "parser" + "annotations", + "metadata", + "xml", + "yaml" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2016-12-05T10:18:33+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v2.12.1", + "name": "jms/parser-lib", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6" + "url": "https://github.com/schmittjoh/parser-lib.git", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/beef6cbe6dec7205edcd143842a49f9a691859a6", - "reference": "beef6cbe6dec7205edcd143842a49f9a691859a6", + "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", "shasum": "" }, "require": { - "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.0", - "doctrine/annotations": "^1.2", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^5.6 || >=7.0 <7.3", - "php-cs-fixer/diff": "^1.3", - "symfony/console": "^3.2 || ^4.0", - "symfony/event-dispatcher": "^3.0 || ^4.0", - "symfony/filesystem": "^3.0 || ^4.0", - "symfony/finder": "^3.0 || ^4.0", - "symfony/options-resolver": "^3.0 || ^4.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^3.0 || ^4.0", - "symfony/stopwatch": "^3.0 || ^4.0" + "phpoption/phpoption": ">=0.9,<2.0-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "description": "A library for easily creating recursive-descent parsers.", + "time": "2012-11-18T18:08:43+00:00" + }, + { + "name": "jms/serializer", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/serializer.git", + "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/00863e1d55b411cc33ad3e1de09a4c8d3aae793c", + "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/instantiator": "^1.0.3", + "jms/metadata": "^1.3", + "jms/parser-lib": "1.*", + "php": "^5.5|^7.0", + "phpcollection/phpcollection": "~0.1", + "phpoption/phpoption": "^1.1" }, "conflict": { - "hhvm": "*" + "twig/twig": "<1.12" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.1", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.1", - "php-cs-fixer/accessible-object": "^1.0", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1", - "phpunitgoodpractices/traits": "^1.5", - "symfony/phpunit-bridge": "^4.0" + "doctrine/orm": "~2.1", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "phpunit/phpunit": "^4.8|^5.0", + "propel/propel1": "~1.7", + "psr/container": "^1.0", + "symfony/dependency-injection": "^2.7|^3.3|^4.0", + "symfony/expression-language": "^2.6|^3.0", + "symfony/filesystem": "^2.1", + "symfony/form": "~2.1|^3.0", + "symfony/translation": "^2.1|^3.0", + "symfony/validator": "^2.2|^3.0", + "symfony/yaml": "^2.1|^3.0", + "twig/twig": "~1.12|~2.0" }, "suggest": { - "ext-mbstring": "For handling non-UTF8 characters in cache signature.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "doctrine/cache": "Required if you like to use cache functionality.", + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", + "symfony/yaml": "Required if you'd like to serialize data to YAML format." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.13-dev" + } }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/Assert/AssertTokensTrait.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/TestCase.php" - ] + "psr-0": { + "JMS\\Serializer": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4745,16 +6196,89 @@ ], "authors": [ { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" }, { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "A tool to automatically fix PHP code style", - "time": "2018-06-10T08:26:56+00:00" + "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", + "homepage": "http://jmsyst.com/libs/serializer", + "keywords": [ + "deserialization", + "jaxb", + "json", + "serialization", + "xml" + ], + "time": "2018-07-25T13:58:54+00:00" + }, + { + "name": "league/container", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "time": "2017-05-10T09:20:27+00:00" }, { "name": "lusitanian/oauth", @@ -4823,6 +6347,172 @@ ], "time": "2018-02-14T22:37:14+00:00" }, + { + "name": "magento/magento2-functional-testing-framework", + "version": "2.3.5", + "source": { + "type": "git", + "url": "https://github.com/magento/magento2-functional-testing-framework.git", + "reference": "bb1518aab82464e25ff97874da939d13ba4b6fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/bb1518aab82464e25ff97874da939d13ba4b6fac", + "reference": "bb1518aab82464e25ff97874da939d13ba4b6fac", + "shasum": "" + }, + "require": { + "allure-framework/allure-codeception": "~1.2.6", + "codeception/codeception": "~2.3.4", + "consolidation/robo": "^1.0.0", + "epfremme/swagger-php": "^2.0", + "flow/jsonpath": ">0.2", + "fzaninotto/faker": "^1.6", + "monolog/monolog": "^1.0", + "mustache/mustache": "~2.5", + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0", + "vlucas/phpdotenv": "^2.4" + }, + "require-dev": { + "brainmaestro/composer-git-hooks": "^2.3", + "codacy/coverage": "^1.4", + "codeception/aspect-mock": "^3.0", + "doctrine/cache": "<1.7.0", + "goaop/framework": "2.2.0", + "php-coveralls/php-coveralls": "^1.0", + "phpmd/phpmd": "^2.6.0", + "rregeer/phpunit-coverage-check": "^0.1.4", + "sebastian/phpcpd": "~3.0 || ~4.0", + "squizlabs/php_codesniffer": "~3.2", + "symfony/stopwatch": "~3.4.6" + }, + "bin": [ + "bin/mftf" + ], + "type": "library", + "extra": { + "hooks": { + "pre-push": "bin/all-checks" + } + }, + "autoload": { + "files": [ + "src/Magento/FunctionalTestingFramework/_bootstrap.php" + ], + "psr-4": { + "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", + "MFTF\\": "dev/tests/functional/MFTF" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0" + ], + "description": "Magento2 Functional Testing Framework", + "keywords": [ + "automation", + "functional", + "magento", + "testing" + ], + "time": "2018-08-21T16:57:34+00:00" + }, + { + "name": "moontoast/math", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/moontoast-math.git", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^0.9.0", + "phpunit/phpunit": "^4.7|>=5.0 <5.4", + "satooshi/php-coveralls": "^0.6.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Moontoast\\Math\\": "src/Moontoast/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A mathematics library, providing functionality for large numbers", + "homepage": "https://github.com/ramsey/moontoast-math", + "keywords": [ + "bcmath", + "math" + ], + "time": "2017-02-16T16:54:46+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2017-07-11T12:54:05+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.8.1", @@ -5064,6 +6754,54 @@ ], "time": "2018-02-15T16:58:55+00:00" }, + { + "name": "phpcollection/phpcollection", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-collection.git", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "shasum": "" + }, + "require": { + "phpoption/phpoption": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-0": { + "PhpCollection": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "General-Purpose Collection Library for PHP", + "keywords": [ + "collection", + "list", + "map", + "sequence", + "set" + ], + "time": "2015-05-17T12:39:23+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0.1", @@ -5282,18 +7020,68 @@ ], "time": "2017-01-20T14:41:10+00:00" }, + { + "name": "phpoption/phpoption", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-0": { + "PhpOption\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "time": "2015-07-25T16:39:46+00:00" + }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -5305,12 +7093,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -5343,7 +7131,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5596,16 +7384,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.8", + "version": "6.5.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" + "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/24da433d7384824d65ea93fbb462e2f31bbb494e", + "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e", "shasum": "" }, "require": { @@ -5623,7 +7411,7 @@ "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/phpunit-mock-objects": "^5.0.9", "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", @@ -5676,20 +7464,20 @@ "testing", "xunit" ], - "time": "2018-04-10T11:38:34+00:00" + "time": "2018-08-22T06:32:48+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.7", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3eaf040f20154d27d6da59ca2c6e28ac8fd56dce", - "reference": "3eaf040f20154d27d6da59ca2c6e28ac8fd56dce", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { @@ -5702,7 +7490,7 @@ "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -5735,7 +7523,7 @@ "mock", "xunit" ], - "time": "2018-05-29T13:50:43+00:00" + "time": "2018-08-09T05:50:03+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6314,158 +8102,396 @@ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", "shasum": "" }, - "require": { - "php": ">=5.6.0" + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d86873af43b4aa9d1f39a3601cc0cfcf02b25266", + "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-06-06T23:58:19+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v4.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "c55fe9257003b2d95c0211b3f6941e8dfd26dffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c55fe9257003b2d95c0211b3f6941e8dfd26dffd", + "reference": "c55fe9257003b2d95c0211b3f6941e8dfd26dffd", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/dom-crawler": "~3.4|~4.0" + }, + "require-dev": { + "symfony/css-selector": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2018-07-26T09:10:45+00:00" + }, + { + "name": "symfony/config", + "version": "v4.1.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "76015a3cc372b14d00040ff58e18e29f69eba717" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/76015a3cc372b14d00040ff58e18e29f69eba717", + "reference": "76015a3cc372b14d00040ff58e18e29f69eba717", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/finder": "<3.4" + }, + "require-dev": { + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "4.1-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-08-08T06:37:38+00:00" }, { - "name": "sebastian/version", - "version": "2.0.1", + "name": "symfony/css-selector", + "version": "v4.1.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "url": "https://github.com/symfony/css-selector.git", + "reference": "2a4df7618f869b456f9096781e78c57b509d76c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a4df7618f869b456f9096781e78c57b509d76c7", + "reference": "2a4df7618f869b456f9096781e78c57b509d76c7", "shasum": "" }, "require": { - "php": ">=5.6" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.1-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-07-26T09:10:45+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.3.0", + "name": "symfony/dependency-injection", + "version": "v4.1.4", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "bae4983003c9d451e278504d7d9b9d7fc1846873" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d86873af43b4aa9d1f39a3601cc0cfcf02b25266", - "reference": "d86873af43b4aa9d1f39a3601cc0cfcf02b25266", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/bae4983003c9d451e278504d7d9b9d7fc1846873", + "reference": "bae4983003c9d451e278504d7d9b9d7fc1846873", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": "^7.1.3", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<4.1.1", + "symfony/finder": "<3.4", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "psr/container-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/config": "~4.1", + "symfony/expression-language": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.1-dev" } }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2018-06-06T23:58:19+00:00" + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2018-08-08T11:48:58+00:00" }, { - "name": "symfony/config", - "version": "v4.1.1", + "name": "symfony/dom-crawler", + "version": "v4.1.4", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "1c4519d257e652404c3aa550207ccd8ada66b38e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", - "reference": "e57e7b573df9d0eaa8c0152768c708ee7ea2b8e5", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1c4519d257e652404c3aa550207ccd8ada66b38e", + "reference": "1c4519d257e652404c3aa550207ccd8ada66b38e", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/filesystem": "~3.4|~4.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/finder": "<3.4" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/css-selector": "~3.4|~4.0" }, "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/css-selector": "" }, "type": "library", "extra": { @@ -6475,7 +8501,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Config\\": "" + "Symfony\\Component\\DomCrawler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6495,48 +8521,31 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-06-20T11:15:17+00:00" + "time": "2018-07-26T11:00:49+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v4.1.1", + "name": "symfony/http-foundation", + "version": "v4.1.4", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "e761828a85d7dfc00b927f94ccbe1851ce0b6535" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "3a5c91e133b220bb882b3cd773ba91bf39989345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e761828a85d7dfc00b927f94ccbe1851ce0b6535", - "reference": "e761828a85d7dfc00b927f94ccbe1851ce0b6535", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3a5c91e133b220bb882b3cd773ba91bf39989345", + "reference": "3a5c91e133b220bb882b3cd773ba91bf39989345", "shasum": "" }, "require": { "php": "^7.1.3", - "psr/container": "^1.0" - }, - "conflict": { - "symfony/config": "<4.1.1", - "symfony/finder": "<3.4", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" - }, - "provide": { - "psr/container-implementation": "1.0" + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "symfony/config": "~4.1", - "symfony/expression-language": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "predis/predis": "~1.0", + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { @@ -6546,7 +8555,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6566,22 +8575,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-06-25T11:12:43+00:00" + "time": "2018-08-27T17:47:02+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "45cdcc8a96ef92b43a50723e6d1f5f83096e8cef" + "reference": "1913f1962477cdbb13df951f8147d5da1fe2412c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/45cdcc8a96ef92b43a50723e6d1f5f83096e8cef", - "reference": "45cdcc8a96ef92b43a50723e6d1f5f83096e8cef", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/1913f1962477cdbb13df951f8147d5da1fe2412c", + "reference": "1913f1962477cdbb13df951f8147d5da1fe2412c", "shasum": "" }, "require": { @@ -6622,30 +8631,30 @@ "configuration", "options" ], - "time": "2018-05-31T10:17:53+00:00" + "time": "2018-07-26T08:55:25+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0", + "paragonie/random_compat": "~1.0|~2.0|~9.99", "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6681,20 +8690,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46" + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", "shasum": "" }, "require": { @@ -6703,7 +8712,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6736,20 +8745,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.1", + "version": "v4.1.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784" + "reference": "966c982df3cca41324253dc0c7ffe76b6076b705" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/07463bbbbbfe119045a24c4a516f92ebd2752784", - "reference": "07463bbbbbfe119045a24c4a516f92ebd2752784", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/966c982df3cca41324253dc0c7ffe76b6076b705", + "reference": "966c982df3cca41324253dc0c7ffe76b6076b705", "shasum": "" }, "require": { @@ -6785,7 +8794,66 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-19T16:51:42+00:00" + "time": "2018-07-26T11:00:49+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.15", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c2f4812ead9f847cb69e90917ca7502e6892d6b8", + "reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-08-10T07:34:36+00:00" }, { "name": "theseer/fdomdocument", @@ -6867,6 +8935,56 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "time": "2017-04-07T12:08:54+00:00" }, + { + "name": "vlucas/phpdotenv", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2018-07-29T20:33:41+00:00" + }, { "name": "webmozart/assert", "version": "1.3.0", diff --git a/dev/tests/acceptance/.env.example b/dev/tests/acceptance/.env.example deleted file mode 100644 index 432851acbf286..0000000000000 --- a/dev/tests/acceptance/.env.example +++ /dev/null @@ -1,37 +0,0 @@ -#Copyright © Magento, Inc. All rights reserved. -#See COPYING.txt for license details. - -#*** Set the base URL for your Magento instance ***# -MAGENTO_BASE_URL= - -#*** Set the Admin Username and Password for your Magento instance ***# -MAGENTO_BACKEND_NAME= -MAGENTO_ADMIN_USERNAME= -MAGENTO_ADMIN_PASSWORD= - -#*** Path to CLI entry point and command parameter name. Uncomment and change if folder structure differs from standard Magento installation -#MAGENTO_CLI_COMMAND_PATH=dev/tests/acceptance/utils/command.php -#MAGENTO_CLI_COMMAND_PARAMETER=command - -#*** Selenium Server Protocol, Host, Port, and Path, with local defaults. Uncomment and change if not running Selenium locally. -#SELENIUM_HOST=127.0.0.1 -#SELENIUM_PORT=4444 -#SELENIUM_PROTOCOL=http -#SELENIUM_PATH=/wd/hub - -#*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***# -#MAGENTO_RESTAPI_SERVER_HOST= -#MAGENTO_RESTAPI_SERVER_PORT= - -#*** Uncomment these properties to set up a dev environment with symlinked projects ***# -#TESTS_BP= -#FW_BP= -#TESTS_MODULE_PATH= - -#*** These properties impact the modules loaded into MFTF, you can point to your own full path, or a custom set of modules located with the core set -MODULE_WHITELIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch -#CUSTOM_MODULE_PATHS= - -#*** Bool property which allows the user to toggle debug output during test execution -#MFTF_DEBUG= -#*** End of .env ***# diff --git a/dev/tests/acceptance/.gitignore b/dev/tests/acceptance/.gitignore index de6a96d05e7e2..e3ef78df82b9f 100755 --- a/dev/tests/acceptance/.gitignore +++ b/dev/tests/acceptance/.gitignore @@ -1,7 +1,10 @@ .idea .env +.htaccess codeception.yml tests/_output/* tests/functional.suite.yml tests/functional/Magento/FunctionalTest/_generated vendor/* +mftf.log +/utils/ \ No newline at end of file diff --git a/dev/tests/acceptance/LICENSE.txt b/dev/tests/acceptance/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software 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 Open Software 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, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software 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 (C) 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 "Open Software License" or "OSL" 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 <insert your license name here>" 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/dev/tests/acceptance/LICENSE_AFL.txt b/dev/tests/acceptance/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -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 <insert your license name here>" 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/dev/tests/acceptance/README.md b/dev/tests/acceptance/README.md deleted file mode 100755 index 6350b9cabcdfa..0000000000000 --- a/dev/tests/acceptance/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Magento Functional Testing Framework - ----- - -## System Requirements -[Magento Functional Testing Framework system requirements](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html#prepare-environment) - -## Installation -To install the Magento Functional Testing Framework, see [Getting Started](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html) - -## Contributing -Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. - -To learn about how to make a contribution, click [here][1]. - -To open an issue, click [here][2]. - -To suggest documentation improvements, click [here][3]. - -[1]: <http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/contribution-guidelines.html> -[2]: <https://github.com/magento/magento2-functional-testing-framework/issues> -[3]: <http://devdocs.magento.com> - -### Labels applied by the MFTF team - -Refer to the tables with descriptions of each label below. These labels are applied by the MFTF development team to community contributed issues and pull requests, to communicate status, impact, or which team is working on it. - -### Pull Request Status - -Label| Description ----|--- -**accept**| The pull request has been accepted and will be merged into mainline code. -**reject**| The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the pull request. - -### Issue Resolution Status - -Label| Description ----|--- -**acknowledged**| The Magento Team has validated the issue and an internal ticket has been created. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request. -**cannot reproduce**| The Magento Team has not confirmed that this issue contains the minimum required information to reproduce. -**non-issue**| The Magento Team has not recognised any issue according to provided information. - -### Domains Impacted - -Label| Description ----|--- -**PROD**| Affects the Product team (mostly feature requests or business logic change). -**DOC**| Affects Documentation domain. -**TECH**| Affects Architect Group (mostly to make decisions around technology changes). - -### Type - -Label| Description ----|--- -**bugfix**| The issue or pull request relates to bug fixing. -**enhancement**| The issue or pull request that raises the MFTF to a higher degree (for example new features, optimization, refactoring, etc). - -## License - -Each Magento source file included in this distribution is licensed under APL 3.0 - -Please see LICENSE_APL3.txt for the full text of the APL 3.0 license or contact license@magentocommerce.com for a copy. diff --git a/dev/tests/acceptance/RoboFile.php b/dev/tests/acceptance/RoboFile.php index dd84b4131e502..e6e9e591bbd8b 100644 --- a/dev/tests/acceptance/RoboFile.php +++ b/dev/tests/acceptance/RoboFile.php @@ -15,84 +15,6 @@ class RoboFile extends \Robo\Tasks { use Robo\Task\Base\loadShortcuts; - public function __construct() - { - require 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; - define('VENDOR_BIN_PATH', PROJECT_ROOT . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR); - - } - /** - * Duplicate the Example configuration files used to customize the Project for customization. - * - * @return void - */ - function cloneFiles() - { - $this->_exec('cp -vn .env.example .env'); - $this->_exec('cp -vf codeception.dist.yml codeception.yml'); - $this->_exec('cp -vf tests'. DIRECTORY_SEPARATOR .'functional.suite.dist.yml tests'. DIRECTORY_SEPARATOR .'functional.suite.yml'); - } - - /** - * Finds relative paths between codeception.yml file and MFTF path, and overwrites the default paths. - * - * @return void - */ - private function buildCodeceptionPaths() - { - $relativePathFunc = function ($from, $to) - { - $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; - $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; - $from = str_replace('\\', '/', $from); - $to = str_replace('\\', '/', $to); - - $from = explode('/', $from); - $to = explode('/', $to); - $relPath = $to; - - foreach($from as $depth => $dir) { - // find first non-matching dir - if($dir === $to[$depth]) { - // ignore this directory - array_shift($relPath); - } else { - // get number of remaining dirs to $from - $remaining = count($from) - $depth; - if($remaining > 1) { - // add traversals up to first matching dir - $padLength = (count($relPath) + $remaining - 1) * -1; - $relPath = array_pad($relPath, $padLength, '..'); - break; - } else { - $relPath[0] = './' . $relPath[0]; - } - } - } - return implode('/', $relPath); - }; - - //Find travel path from codeception.yml to FW_BP - $configYmlPath = dirname(dirname(TESTS_BP)) . DIRECTORY_SEPARATOR; - $relativePath = call_user_func($relativePathFunc, $configYmlPath, FW_BP); - $configYmlFile = $configYmlPath . "codeception.yml"; - $defaultConfigYmlFile = $configYmlPath . "codeception.dist.yml"; - - if (file_exists($configYmlFile)) { - $ymlContents = file_get_contents($configYmlFile); - } else { - $ymlContents = file_get_contents($defaultConfigYmlFile); - } - $ymlArray = Yaml::parse($ymlContents) ?? []; - if (!array_key_exists("paths", $ymlArray)) { - $ymlArray["paths"] = []; - } - $ymlArray["paths"]["support"] = $relativePath . 'src/Magento/FunctionalTestingFramework'; - $ymlArray["paths"]["envs"] = $relativePath . 'etc/_envs'; - $ymlText = Yaml::dump($ymlArray, 10); - file_put_contents($configYmlFile, $ymlText); - } - /** * Duplicate the Example configuration files for the Project. * Build the Codeception project. @@ -101,9 +23,7 @@ private function buildCodeceptionPaths() */ function buildProject() { - $this->cloneFiles(); - $this->buildCodeceptionPaths(); - $this->_exec(VENDOR_BIN_PATH .'codecept build'); + passthru($this->getBaseCmd("build:project")); } /** @@ -111,99 +31,32 @@ function buildProject() * * @param array $tests * @param array $opts - * @return void + * @return \Robo\Result */ function generateTests(array $tests, $opts = [ 'config' => null, 'force' => false, 'nodes' => null, - 'lines' => 500, + 'lines' => null, 'tests' => null ]) { - require 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; - $testConfiguration = $this->createTestConfiguration($tests, $opts); - - // maintain backwards compatability for devops by not removing the nodes option yet - $lines = $opts['lines']; - - // create our manifest file here - $testManifest = \Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory::makeManifest($opts['config'],$testConfiguration['suites']); - \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance(null, $testConfiguration['tests'])->createAllTestFiles($testManifest); - - if ($opts['config'] == 'parallel') { - $testManifest->createTestGroups($lines); - } - - \Magento\FunctionalTestingFramework\Suite\SuiteGenerator::getInstance()->generateAllSuites($testManifest); - $testManifest->generate(); - - $this->say("Generate Tests Command Run"); - } - + $baseCmd = $this->getBaseCmd("generate:tests"); - /** - * Function which builds up a configuration including test and suites for consumption of Magento generation methods. - * - * @param array $tests - * @param array $opts - * @return array - */ - private function createTestConfiguration($tests, $opts) - { - // set our application configuration so we can references the user options in our framework - Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::create( - $opts['force'], - Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::GENERATION_PHASE, - $opts['verbose'] - ); - - $testConfiguration = []; - $testConfiguration['tests'] = $tests; - $testConfiguration['suites'] = []; - - $testConfiguration = $this->parseTestsConfigJson($opts['tests'], $testConfiguration); - - // if we have references to specific tests, we resolve the test objects and pass them to the config - if (!empty($testConfiguration['tests'])) - { - $testObjects = []; - - foreach ($testConfiguration['tests'] as $test) - { - $testObjects[$test] = Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler::getInstance()->getObject($test); + $mftfArgNames = ['config', 'nodes', 'lines', 'tests']; + // append arguments to the end of the command + foreach ($opts as $argName => $argValue) { + if (in_array($argName, $mftfArgNames) && $argValue !== null) { + $baseCmd .= " --$argName $argValue"; } - - $testConfiguration['tests'] = $testObjects; - } - - return $testConfiguration; - } - - /** - * Function which takes a json string of potential custom configuration and parses/validates the resulting json - * passed in by the user. The result is a testConfiguration array. - * - * @param string $json - * @param array $testConfiguration - * @return array - */ - private function parseTestsConfigJson($json, $testConfiguration) { - if ($json == null) { - return $testConfiguration; } - $jsonTestConfiguration = []; - $testConfigArray = json_decode($json, true); - - // stop execution if we have failed to properly parse any json - if (json_last_error() != JSON_ERROR_NONE) { - throw new \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException("JSON could not be parsed: " . json_last_error_msg()); + // use a separate conditional for the force flag (casting bool to string in php is hard) + if ($opts['force']) { + $baseCmd .= ' --force'; } - $jsonTestConfiguration['tests'] = $testConfigArray['tests'] ?? null;; - $jsonTestConfiguration['suites'] = $testConfigArray['suites'] ?? null; - return $jsonTestConfiguration; + return $this->taskExec($baseCmd)->args($tests)->run(); } /** @@ -211,61 +64,28 @@ private function parseTestsConfigJson($json, $testConfiguration) { * * @param array $args * @throws Exception - * @return void + * @return \Robo\Result */ function generateSuite(array $args) { if (empty($args)) { throw new Exception("Please provide suite name(s) after generate:suite command"); } - - $sg = \Magento\FunctionalTestingFramework\Suite\SuiteGenerator::getInstance(); - - foreach ($args as $arg) { - $sg->generateSuite($arg); - } - } - - /** - * Run all Functional tests. - * - * @return void - */ - function functional() - { - $this->_exec(VENDOR_BIN_PATH . 'codecept run functional'); + $baseCmd = $this->getBaseCmd("generate:suite"); + return $this->taskExec($baseCmd)->args($args)->run(); } /** * Run all Tests with the specified @group tag'. * - * @param string $args - * @return void - */ - function group($args = '') - { - $this->taskExec(VENDOR_BIN_PATH . 'codecept run functional --verbose --steps --group')->args($args)->run(); - } - - /** - * Run all Functional tests located under the Directory Path provided. - * - * @param string $args - * @return void - */ - function folder($args = '') - { - $this->taskExec(VENDOR_BIN_PATH . 'codecept run functional')->args($args)->run(); - } - - /** - * Run all Tests marked with the @group tag 'example'. - * - * @return void + * @param array $args + * @return \Robo\Result */ - function example() + function group(array $args) { - $this->_exec(VENDOR_BIN_PATH . 'codecept run --group example'); + $args = array_merge($args, ['-k']); + $baseCmd = $this->getBaseCmd("run:group"); + return $this->taskExec($baseCmd)->args($args)->run(); } /** @@ -291,58 +111,65 @@ function allure2Generate() /** * Open the HTML Allure report - Allure v1.4.X * - * @return void + * @return \Robo\Result */ function allure1Open() { - $this->_exec('allure report open --report-dir tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); + return $this->_exec('allure report open --report-dir tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); } /** * Open the HTML Allure report - Allure v2.3.X * - * @return void + * @return \Robo\Result */ function allure2Open() { - $this->_exec('allure open --port 0 tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); + return $this->_exec('allure open --port 0 tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); } /** * Generate and open the HTML Allure report - Allure v1.4.X * - * @return void + * @return \Robo\Result */ function allure1Report() { $result1 = $this->allure1Generate(); if ($result1->wasSuccessful()) { - $this->allure1Open(); + return $this->allure1Open(); + } else { + return $result1; } } /** * Generate and open the HTML Allure report - Allure v2.3.X * - * @return void + * @return \Robo\Result */ function allure2Report() { $result1 = $this->allure2Generate(); if ($result1->wasSuccessful()) { - $this->allure2Open(); + return $this->allure2Open(); + } else { + return $result1; } } /** - * Run the Pre-Install system check script. + * Private function for returning the formatted command for the passthru to mftf bin execution. * - * @return void + * @param string $command + * @return string */ - function preInstall() + private function getBaseCmd($command) { - $this->_exec('php pre-install.php'); + $this->writeln("\033[01;31m Use of robo will be deprecated with next major release, please use <root>/vendor/bin/mftf $command \033[0m"); + chdir(__DIR__); + return realpath('../../../vendor/bin/mftf') . " $command"; } -} +} \ No newline at end of file diff --git a/dev/tests/acceptance/codeception.dist.yml b/dev/tests/acceptance/codeception.dist.yml deleted file mode 100644 index 683edcb107fd1..0000000000000 --- a/dev/tests/acceptance/codeception.dist.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. -actor: Tester -paths: - tests: tests - log: tests/_output - data: tests/_data - support: "%REPLACED IN BUILD:PROJECT%" - envs: "%REPLACED IN BUILD:PROJECT%" -settings: - bootstrap: _bootstrap.php - colors: true - memory_limit: 1024M -extensions: - enabled: - - Codeception\Extension\RunFailed - - Magento\FunctionalTestingFramework\Extension\TestContextExtension - - Magento\FunctionalTestingFramework\Allure\Adapter\MagentoAllureAdapter - config: - Yandex\Allure\Adapter\AllureAdapter: - deletePreviousResults: true - outputDirectory: allure-results - ignoredAnnotations: - - env - - zephyrId - - useCaseId -params: - - .env -modules: - config: - Db: - dsn: "%DB_DSN%" - user: "%DB_USERNAME%" - password: "%DB_PASSWORD%" - dump: tests/_data/dump.sql \ No newline at end of file diff --git a/dev/tests/acceptance/composer.json b/dev/tests/acceptance/composer.json index 787d2579e03ca..83cad123f8568 100755 --- a/dev/tests/acceptance/composer.json +++ b/dev/tests/acceptance/composer.json @@ -9,20 +9,17 @@ "config": { "sort-packages": true }, - "repositories": [ - { - "type": "git", - "url": "git@github.com:magento/magento2-functional-testing-framework.git" - } - ], "require": { "php": "~7.1.3||~7.2.0", - "magento/magento2-functional-testing-framework": "~2.2.0" + "codeception/codeception": "~2.3.4 || ~2.4.0", + "consolidation/robo": "^1.0.0", + "vlucas/phpdotenv": "^2.4" }, "autoload": { "psr-4": { "Magento\\": "tests/functional/Magento" - } + }, + "files": ["tests/_bootstrap.php"] }, "prefer-stable": true } diff --git a/dev/tests/acceptance/composer.lock b/dev/tests/acceptance/composer.lock index afe2640b442f1..8542402a98f50 100644 --- a/dev/tests/acceptance/composer.lock +++ b/dev/tests/acceptance/composer.lock @@ -4,111 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9f292f6e226938ad82d1189da7aa4024", + "content-hash": "46ca2d50566f5069daef753664080c5a", "packages": [ - { - "name": "allure-framework/allure-codeception", - "version": "1.2.7", - "source": { - "type": "git", - "url": "https://github.com/allure-framework/allure-codeception.git", - "reference": "48598f4b4603b50b663bfe977260113a40912131" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", - "reference": "48598f4b4603b50b663bfe977260113a40912131", - "shasum": "" - }, - "require": { - "allure-framework/allure-php-api": "~1.1.0", - "codeception/codeception": "~2.1", - "php": ">=5.4.0", - "symfony/filesystem": ">=2.6", - "symfony/finder": ">=2.6" - }, - "type": "library", - "autoload": { - "psr-0": { - "Yandex": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", - "role": "Developer" - } - ], - "description": "A Codeception adapter for Allure report.", - "homepage": "http://allure.qatools.ru/", - "keywords": [ - "allure", - "attachments", - "cases", - "codeception", - "report", - "steps", - "testing" - ], - "time": "2018-03-07T11:18:27+00:00" - }, - { - "name": "allure-framework/allure-php-api", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/allure-framework/allure-php-adapter-api.git", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", - "shasum": "" - }, - "require": { - "jms/serializer": ">=0.16.0", - "moontoast/math": ">=1.1.0", - "php": ">=5.4.0", - "phpunit/phpunit": ">=4.0.0", - "ramsey/uuid": ">=3.0.0", - "symfony/http-foundation": ">=2.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Yandex": [ - "src/", - "test/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", - "role": "Developer" - } - ], - "description": "PHP API for Allure adapter", - "homepage": "http://allure.qatools.ru/", - "keywords": [ - "allure", - "api", - "php", - "report" - ], - "time": "2016-12-07T12:15:46+00:00" - }, { "name": "behat/gherkin", "version": "v4.4.5", @@ -264,16 +161,16 @@ }, { "name": "codeception/stub", - "version": "1.0.2", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/Codeception/Stub.git", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99" + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/95fb7a36b81890dd2e5163e7ab31310df6f1bb99", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", "shasum": "" }, "require": { @@ -293,20 +190,20 @@ "MIT" ], "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-02-18T13:56:56+00:00" + "time": "2018-05-17T09:31:08+00:00" }, { "name": "consolidation/annotated-command", - "version": "2.8.3", + "version": "2.8.4", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437" + "reference": "651541a0b68318a2a202bda558a676e5ad92223c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", - "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/651541a0b68318a2a202bda558a676e5ad92223c", + "reference": "651541a0b68318a2a202bda558a676e5ad92223c", "shasum": "" }, "require": { @@ -318,9 +215,9 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "phpunit/phpunit": "^4.8", - "satooshi/php-coveralls": "^1.0.2 | dev-master", + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^6", + "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7" }, "type": "library", @@ -345,20 +242,20 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-02-23T16:32:04+00:00" + "time": "2018-05-25T18:04:25+00:00" }, { "name": "consolidation/config", - "version": "1.0.9", + "version": "1.0.11", "source": { "type": "git", "url": "https://github.com/consolidation/config.git", - "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c" + "reference": "ede41d946078e97e7a9513aadc3352f1c26817af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/34ca8d7c1ee60a7b591b10617114cf1210a2e92c", - "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c", + "url": "https://api.github.com/repos/consolidation/config/zipball/ede41d946078e97e7a9513aadc3352f1c26817af", + "reference": "ede41d946078e97e7a9513aadc3352f1c26817af", "shasum": "" }, "require": { @@ -367,7 +264,7 @@ "php": ">=5.4.0" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", + "g1a/composer-test-scenarios": "^1", "phpunit/phpunit": "^4", "satooshi/php-coveralls": "^1.0", "squizlabs/php_codesniffer": "2.*", @@ -399,20 +296,20 @@ } ], "description": "Provide configuration services for a commandline tool.", - "time": "2017-12-22T17:28:19+00:00" + "time": "2018-05-27T01:17:02+00:00" }, { "name": "consolidation/log", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/consolidation/log.git", - "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821" + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/dbc7c535f319a4a2d5a5077738f8eb7c10df8821", - "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821", + "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", "shasum": "" }, "require": { @@ -421,8 +318,9 @@ "symfony/console": "^2.8|^3|^4" }, "require-dev": { + "g1a/composer-test-scenarios": "^1", "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "dev-master", + "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "2.*" }, "type": "library", @@ -447,20 +345,20 @@ } ], "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2017-11-29T01:44:16+00:00" + "time": "2018-05-25T18:14:39+00:00" }, { "name": "consolidation/output-formatters", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9" + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/da889e4bce19f145ca4ec5b1725a946f4eb625a9", - "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", "shasum": "" }, "require": { @@ -469,7 +367,7 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g-1-a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^2", "phpunit/phpunit": "^5.7.27", "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7", @@ -502,25 +400,25 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-03-20T15:18:32+00:00" + "time": "2018-05-25T18:02:34+00:00" }, { "name": "consolidation/robo", - "version": "1.2.3", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "54a13e268917b92576d75e10dca8227b95a574d9" + "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/54a13e268917b92576d75e10dca8227b95a574d9", - "reference": "54a13e268917b92576d75e10dca8227b95a574d9", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/ac563abfadf7cb7314b4e152f2b5033a6c255f6f", + "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f", "shasum": "" }, "require": { "consolidation/annotated-command": "^2.8.2", - "consolidation/config": "^1.0.1", + "consolidation/config": "^1.0.10", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", "grasmash/yaml-expander": "^1.3", @@ -539,7 +437,7 @@ "codeception/aspect-mock": "^1|^2.1.1", "codeception/base": "^2.3.7", "codeception/verify": "^0.3.2", - "g-1-a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^2", "goaop/framework": "~2.1.2", "goaop/parser-reflection": "^1.1.0", "natxet/cssmin": "3.0.4", @@ -582,7 +480,7 @@ } ], "description": "Modern task runner", - "time": "2018-04-06T05:27:37+00:00" + "time": "2018-05-27T01:42:53+00:00" }, { "name": "container-interop/container-interop", @@ -674,141 +572,6 @@ ], "time": "2017-01-20T21:14:22+00:00" }, - { - "name": "doctrine/annotations", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2017-12-06T07:11:42+00:00" - }, - { - "name": "doctrine/collections", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "array", - "collections", - "iterator" - ], - "time": "2017-07-22T10:37:32+00:00" - }, { "name": "doctrine/instantiator", "version": "1.1.0", @@ -863,136 +626,41 @@ ], "time": "2017-07-22T11:58:36+00:00" }, - { - "name": "doctrine/lexer", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2014-09-09T13:34:57+00:00" - }, - { - "name": "epfremme/swagger-php", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/epfremmer/swagger-php.git", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.2", - "doctrine/collections": "^1.3", - "jms/serializer": "^1.1", - "php": ">=5.5", - "phpoption/phpoption": "^1.1", - "symfony/yaml": "^2.7|^3.1" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "~4.8|~5.0", - "satooshi/php-coveralls": "^1.0" - }, - "type": "package", - "autoload": { - "psr-4": { - "Epfremme\\Swagger\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Edward Pfremmer", - "email": "epfremme@nerdery.com" - } - ], - "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", - "time": "2016-09-26T17:24:17+00:00" - }, { "name": "facebook/webdriver", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605" + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/86b5ca2f67173c9d34340845dd690149c886a605", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", "shasum": "" }, "require": { "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", "ext-zip": "*", "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1 || ^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", - "guzzle/guzzle": "^3.4.1", - "php-coveralls/php-coveralls": "^1.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "php-coveralls/php-coveralls": "^2.0", "php-mock/php-mock-phpunit": "^1.1", "phpunit/phpunit": "^5.7", "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", "squizlabs/php_codesniffer": "^2.6", "symfony/var-dumper": "^3.3 || ^4.0" }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, "type": "library", "extra": { "branch-alias": { @@ -1016,98 +684,7 @@ "selenium", "webdriver" ], - "time": "2017-11-15T11:08:09+00:00" - }, - { - "name": "flow/jsonpath", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/FlowCommunications/JSONPath.git", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "peekmo/jsonpath": "dev-master", - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Flow\\JSONPath": "src/", - "Flow\\JSONPath\\Test": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stephen Frank", - "email": "stephen@flowsa.com" - } - ], - "description": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2018-03-04T16:39:47+00:00" - }, - { - "name": "fzaninotto/faker", - "version": "v1.7.1", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", - "squizlabs/php_codesniffer": "^1.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2017-08-15T16:48:10+00:00" + "time": "2018-05-16T17:37:13+00:00" }, { "name": "grasmash/expander", @@ -1206,16 +783,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.3.2", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90" + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/68d0ea14d5a3f42a20e87632a5f84931e2709c90", - "reference": "68d0ea14d5a3f42a20e87632a5f84931e2709c90", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", "shasum": "" }, "require": { @@ -1225,7 +802,7 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", "psr/log": "^1.0" }, "suggest": { @@ -1267,7 +844,7 @@ "rest", "web service" ], - "time": "2018-03-26T16:33:04+00:00" + "time": "2018-04-22T15:46:56+00:00" }, { "name": "guzzlehttp/promises", @@ -1375,185 +952,15 @@ ], "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ - "http", - "message", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2017-03-20T17:10:46+00:00" - }, - { - "name": "jms/metadata", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Metadata\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Class/method/property metadata management in PHP", - "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" - ], - "time": "2016-12-05T10:18:33+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18T18:08:43+00:00" - }, - { - "name": "jms/serializer", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/e7c53477ff55c21d1b1db7d062edc050a24f465f", - "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0", - "doctrine/instantiator": "^1.0.3", - "jms/metadata": "~1.1", - "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" - }, - "require-dev": { - "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "^1.3|^2.0", - "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", - "propel/propel1": "~1.7", - "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" - }, - "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", - "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", - "symfony/yaml": "Required if you'd like to serialize data to YAML format." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", - "homepage": "http://jmsyst.com/libs/serializer", - "keywords": [ - "deserialization", - "jaxb", - "json", - "serialization", - "xml" + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" ], - "time": "2018-02-04T17:48:54+00:00" + "time": "2017-03-20T17:10:46+00:00" }, { "name": "league/container", @@ -1620,194 +1027,30 @@ ], "time": "2017-05-10T09:20:27+00:00" }, - { - "name": "magento/magento2-functional-testing-framework", - "version": "2.2.0", - "source": { - "type": "git", - "url": "git@github.com:magento/magento2-functional-testing-framework.git", - "reference": "4dd196d745bf836cbf0c5904a1df6dd241124309" - }, - "require": { - "allure-framework/allure-codeception": "~1.2.6", - "codeception/codeception": "~2.3.4", - "consolidation/robo": "^1.0.0", - "epfremme/swagger-php": "^2.0", - "flow/jsonpath": ">0.2", - "fzaninotto/faker": "^1.6", - "mustache/mustache": "~2.5", - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0", - "vlucas/phpdotenv": "^2.4" - }, - "require-dev": { - "brainmaestro/composer-git-hooks": "^2.3", - "codacy/coverage": "^1.4", - "codeception/aspect-mock": "^2.0", - "goaop/framework": "2.1.2", - "php-coveralls/php-coveralls": "^1.0", - "phpmd/phpmd": "^2.6.0", - "rregeer/phpunit-coverage-check": "^0.1.4", - "sebastian/phpcpd": "~3.0", - "squizlabs/php_codesniffer": "1.5.3", - "symfony/stopwatch": "~3.4.6" - }, - "bin": [ - "bin/mftf" - ], - "type": "library", - "extra": { - "hooks": { - "pre-push": "bin/all-checks" - } - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", - "MFTF\\": "dev/tests/functional/MFTF" - } - }, - "autoload-dev": { - "psr-4": { - "tests\\unit\\": "dev/tests/unit" - } - }, - "scripts": { - "tests": [ - "bin/phpunit-checks" - ], - "static": [ - "bin/static-checks" - ] - }, - "license": [ - "AGPL-3.0" - ], - "description": "Magento2 Functional Testing Framework", - "keywords": [ - "automation", - "functional", - "magento", - "testing" - ], - "time": "2018-04-24T01:46:09+00:00" - }, - { - "name": "moontoast/math", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/ramsey/moontoast-math.git", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "shasum": "" - }, - "require": { - "ext-bcmath": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "jakub-onderka/php-parallel-lint": "^0.9.0", - "phpunit/phpunit": "^4.7|>=5.0 <5.4", - "satooshi/php-coveralls": "^0.6.1", - "squizlabs/php_codesniffer": "^2.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Moontoast\\Math\\": "src/Moontoast/Math/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A mathematics library, providing functionality for large numbers", - "homepage": "https://github.com/ramsey/moontoast-math", - "keywords": [ - "bcmath", - "math" - ], - "time": "2017-02-16T16:54:46+00:00" - }, - { - "name": "mustache/mustache", - "version": "v2.12.0", - "source": { - "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Mustache": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" - } - ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", - "keywords": [ - "mustache", - "templating" - ], - "time": "2017-07-11T12:54:05+00:00" - }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -1830,55 +1073,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v2.0.12", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "pseudorandom", - "random" - ], - "time": "2018-04-04T21:24:14+00:00" + "time": "2018-06-11T23:09:50+00:00" }, { "name": "phar-io/manifest", @@ -1982,54 +1177,6 @@ "description": "Library for handling version information and constraints", "time": "2017-03-05T17:38:23+00:00" }, - { - "name": "phpcollection/phpcollection", - "version": "0.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "shasum": "" - }, - "require": { - "phpoption/phpoption": "1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-0": { - "PhpCollection": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "General-Purpose Collection Library for PHP", - "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" - ], - "time": "2015-05-17T12:39:23+00:00" - }, { "name": "phpdocumentor/reflection-common", "version": "1.0.1", @@ -2182,56 +1329,6 @@ ], "time": "2017-07-14T14:27:02+00:00" }, - { - "name": "phpoption/phpoption", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-0": { - "PhpOption\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "time": "2015-07-25T16:39:46+00:00" - }, { "name": "phpspec/prophecy", "version": "1.7.6", @@ -2546,16 +1643,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.8", + "version": "6.5.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" + "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/093ca5508174cd8ab8efe44fd1dde447adfdec8f", + "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f", "shasum": "" }, "require": { @@ -2626,20 +1723,20 @@ "testing", "xunit" ], - "time": "2018-04-10T11:38:34+00:00" + "time": "2018-07-03T06:40:40+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", + "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", "shasum": "" }, "require": { @@ -2685,7 +1782,7 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-07-13T03:27:23+00:00" }, { "name": "psr/container", @@ -2833,86 +1930,6 @@ ], "time": "2016-10-10T12:19:37+00:00" }, - { - "name": "ramsey/uuid", - "version": "3.7.3", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", - "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "^1.0|^2.0", - "php": "^5.4 || ^7.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0", - "squizlabs/php_codesniffer": "^2.3" - }, - "suggest": { - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "time": "2018-01-20T00:28:24+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -3474,16 +2491,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "c43bfa0182363b3fd64331b5e64e467349ff4670" + "reference": "ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c43bfa0182363b3fd64331b5e64e467349ff4670", - "reference": "c43bfa0182363b3fd64331b5e64e467349ff4670", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62", + "reference": "ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62", "shasum": "" }, "require": { @@ -3500,7 +2517,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3527,20 +2544,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:35:49+00:00" + "time": "2018-06-04T17:31:56+00:00" }, { "name": "symfony/console", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64" + "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aad9a6fe47319f22748fd764f52d3a7ca6fa6b64", - "reference": "aad9a6fe47319f22748fd764f52d3a7ca6fa6b64", + "url": "https://api.github.com/repos/symfony/console/zipball/5c31f6a97c1c240707f6d786e7e59bfacdbc0219", + "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219", "shasum": "" }, "require": { @@ -3560,7 +2577,7 @@ "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log": "For using the console logger", + "psr/log-implementation": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -3568,7 +2585,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3595,20 +2612,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:24:00+00:00" + "time": "2018-07-16T14:05:40+00:00" }, { "name": "symfony/css-selector", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2" + "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/03f965583147957f1ecbad7ea1c9d6fd5e525ec2", - "reference": "03f965583147957f1ecbad7ea1c9d6fd5e525ec2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/03ac71606ecb0b0ce792faa17d74cc32c2949ef4", + "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4", "shasum": "" }, "require": { @@ -3617,7 +2634,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3648,24 +2665,25 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:35:49+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "d6c04c7532535b5e0b63db45b543cd60818e0fbc" + "reference": "eb501fa8aab8c8e2db790d8d0f945697769f6c41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d6c04c7532535b5e0b63db45b543cd60818e0fbc", - "reference": "d6c04c7532535b5e0b63db45b543cd60818e0fbc", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/eb501fa8aab8c8e2db790d8d0f945697769f6c41", + "reference": "eb501fa8aab8c8e2db790d8d0f945697769f6c41", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { @@ -3677,7 +2695,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3704,20 +2722,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:35:49+00:00" + "time": "2018-07-05T11:54:23+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "63353a71073faf08f62caab4e6889b06a787f07b" + "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/63353a71073faf08f62caab4e6889b06a787f07b", - "reference": "63353a71073faf08f62caab4e6889b06a787f07b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", + "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", "shasum": "" }, "require": { @@ -3740,7 +2758,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3767,29 +2785,30 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:43+00:00" + "time": "2018-07-10T11:02:47+00:00" }, { "name": "symfony/filesystem", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21" + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21", - "reference": "5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3816,20 +2835,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-02-22T10:50:29+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "symfony/finder", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49" + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ca27c02b7a3fef4828c998c2ff9ba7aae1641c49", - "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", + "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", "shasum": "" }, "require": { @@ -3838,7 +2857,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -3865,41 +2884,37 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-04-04T05:10:37+00:00" + "time": "2018-06-19T21:38:16+00:00" }, { - "name": "symfony/http-foundation", - "version": "v4.0.8", + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0864a82e5891ab61d31eecbaa48bed5a09b8e6c", - "reference": "d0864a82e5891ab61d31eecbaa48bed5a09b8e6c", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.1" - }, - "require-dev": { - "symfony/expression-language": "~3.4|~4.0" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "1.8-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Polyfill\\Ctype\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3907,31 +2922,37 @@ "MIT" ], "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" } ], - "description": "Symfony HttpFoundation Component", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", - "time": "2018-04-03T05:24:00+00:00" + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + "reference": "3296adf6a6454a050679cde90f95350ad604b171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -3943,7 +2964,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -3977,20 +2998,20 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/process", - "version": "v4.0.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25" + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25", - "reference": "d7dc1ee5dfe9f732cb1bba7310f5b99f2b7a6d25", + "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", "shasum": "" }, "require": { @@ -3999,7 +3020,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -4026,24 +3047,25 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:24:00+00:00" + "time": "2018-05-31T10:17:53+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.8", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0" + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a42f9da85c7c38d59f5e53f076fe81a091f894d0", - "reference": "a42f9da85c7c38d59f5e53f076fe81a091f894d0", + "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/console": "<3.4" @@ -4057,7 +3079,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -4084,7 +3106,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:14:20+00:00" + "time": "2018-05-30T07:26:09+00:00" }, { "name": "theseer/tokenizer", @@ -4128,28 +3150,28 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.4.0", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", + "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^4.8.35 || ^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -4159,7 +3181,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause-Attribution" + "BSD-3-Clause" ], "authors": [ { @@ -4174,7 +3196,7 @@ "env", "environment" ], - "time": "2016-09-01T10:05:43+00:00" + "time": "2018-07-01T10:25:50+00:00" }, { "name": "webmozart/assert", @@ -4233,6 +3255,8 @@ "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, - "platform": [], + "platform": { + "php": "~7.1.3||~7.2.0" + }, "platform-dev": [] } diff --git a/dev/tests/acceptance/pre-install.php b/dev/tests/acceptance/pre-install.php deleted file mode 100644 index 4021dc1094948..0000000000000 --- a/dev/tests/acceptance/pre-install.php +++ /dev/null @@ -1,395 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * @codingStandardsIgnoreStart - * @SuppressWarnings(PHPMD) - */ -class CliColors { - private $foreground_colors = array(); - private $background_colors = array(); - - public function __construct() { - // Set up shell colors - $this->foreground_colors['black'] = '0;30'; - $this->foreground_colors['dark_gray'] = '1;30'; - $this->foreground_colors['blue'] = '0;34'; - $this->foreground_colors['light_blue'] = '1;34'; - $this->foreground_colors['green'] = '0;32'; - $this->foreground_colors['light_green'] = '1;32'; - $this->foreground_colors['cyan'] = '0;36'; - $this->foreground_colors['light_cyan'] = '1;36'; - $this->foreground_colors['red'] = '0;31'; - $this->foreground_colors['light_red'] = '1;31'; - $this->foreground_colors['purple'] = '0;35'; - $this->foreground_colors['light_purple'] = '1;35'; - $this->foreground_colors['brown'] = '0;33'; - $this->foreground_colors['yellow'] = '1;33'; - $this->foreground_colors['light_gray'] = '0;37'; - $this->foreground_colors['white'] = '1;37'; - - $this->background_colors['black'] = '40'; - $this->background_colors['red'] = '41'; - $this->background_colors['green'] = '42'; - $this->background_colors['yellow'] = '43'; - $this->background_colors['blue'] = '44'; - $this->background_colors['magenta'] = '45'; - $this->background_colors['cyan'] = '46'; - $this->background_colors['light_gray'] = '47'; - } - - /** - * Returns colored string - * - * @param $string - * @param null $foreground_color - * @param null $background_color - * @return string - */ - public function getColoredString($string, $foreground_color = null, $background_color = null) { - $colored_string = ""; - - // Check if given foreground color found - if (isset($this->foreground_colors[$foreground_color])) { - $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m"; - } - // Check if given background color found - if (isset($this->background_colors[$background_color])) { - $colored_string .= "\033[" . $this->background_colors[$background_color] . "m"; - } - - // Add string and end coloring - $colored_string .= $string . "\033[0m"; - - return $colored_string; - } - - /** - * Returns all foreground color names - * - * @return array - */ - public function getForegroundColors() { - return array_keys($this->foreground_colors); - } - - /** - * Returns all background color names - * - * @return array - */ - public function getBackgroundColors() { - return array_keys($this->background_colors); - } -} - -/** - * @SuppressWarnings(PHPMD) - */ -class PreInstallCheck { - private $installedViaBrew = false; - private $filePath = ''; - private $seleniumJarVersion = ''; - - private $phpWebsite = 'http://php.net/manual/en/install.php'; - private $composerWebsite = 'https://getcomposer.org/download/'; - private $javaWebsite = 'https://www.java.com/en/download/'; - private $allureCliWebsite = 'https://docs.qameta.io/allure/latest/#_installing_a_commandline'; - private $seleniumWebsite = 'http://www.seleniumhq.org/download/'; - private $chromeDriverWebsite = 'https://sites.google.com/a/chromium.org/chromedriver/downloads'; - private $geckoDriverWebsite = 'https://github.com/mozilla/geckodriver'; - private $phantomJsWebsite = 'http://phantomjs.org/'; - - private $phpSupportedVersion = '7.1.0'; - private $composerSupportedVersion = '1.3.0'; - private $javaSupportedVersion = '1.8.0'; - private $allureCliSupportedVersion = '2.3.0'; - private $seleniumSupportedVersion = '3.6.0'; - private $chromeDriverSupportedVersion = '2.33.0'; - private $geckoDriverSupportedVersion = '0.19.0'; - private $phantomJsSupportedVersion = '2.1.0'; - - private $getPhpVersion; - private $getComposerVersion; - private $getJavaVersion; - private $getAllureCliVersion; - private $getSeleniumVersion; - private $getChromeDriverVersion; - private $getGeckoDriverVersion; - private $getPhantomJsVersion; - - private $phpVersion; - private $composerVersion; - private $javaVersion; - private $allureCliVersion; - private $seleniumVersion; - private $chromeDriverVersion; - private $geckoDriverVersion; - private $phantomJsVersion; - - private $phpStatus; - private $composerStatus; - private $javaStatus; - private $allureCliStatus; - private $seleniumStatus; - private $chromeDriverStatus; - private $geckoDriverStatus; - private $phantomJsStatus; - - function __construct() { - $this->didYouInstallViaBrew(); - - $this->getPhpVersion = shell_exec('php --version'); - $this->getComposerVersion = shell_exec('composer --version'); - $this->getJavaVersion = shell_exec("java -version 2>&1"); - $this->getAllureCliVersion = shell_exec('allure --version'); - $this->getSeleniumVersion = $this->getSeleniumVersion(); - $this->getChromeDriverVersion = $this->getChromeDriverVersion(); - $this->getGeckoDriverVersion = shell_exec('geckodriver --version'); - $this->getPhantomJsVersion = $this->getPhantomJsVersion(); - - $this->phpVersion = $this->parseVersion($this->getPhpVersion); - $this->composerVersion = $this->parseVersion($this->getComposerVersion); - $this->javaVersion = $this->parseJavaVersion($this->getJavaVersion); - $this->allureCliVersion = $this->parseVersion($this->getAllureCliVersion); - $this->seleniumVersion = $this->parseVersion($this->getSeleniumVersion); - $this->chromeDriverVersion = $this->parseVersion($this->getChromeDriverVersion); - $this->geckoDriverVersion = $this->parseVersion($this->getGeckoDriverVersion); - $this->phantomJsVersion = $this->parseVersion($this->getPhantomJsVersion); - - // String of null Versions - For Testing -// $this->phpVersion = null; -// $this->composerVersion = null; -// $this->javaVersion = null; -// $this->allureCliVersion = null; -// $this->seleniumVersion = null; -// $this->chromeDriverVersion = null; -// $this->geckoDriverVersion = null; -// $this->phantomJsVersion = null; - - // String of invalid Versions - For Testing -// $this->phpVersion = '7.0.0'; -// $this->composerVersion = '1.0.0'; -// $this->javaVersion = '1.0.0'; -// $this->allureCliVersion = '2.0.0'; -// $this->seleniumVersion = '3.0.0'; -// $this->chromeDriverVersion = '2.0.0'; -// $this->geckoDriverVersion = '0.0.0'; -// $this->phantomJsVersion = '2.0.0'; - - $this->phpStatus = $this->verifyVersion('PHP', $this->phpVersion, $this->phpSupportedVersion, $this->phpWebsite); - $this->composerStatus = $this->verifyVersion('Composer', $this->composerVersion, $this->composerSupportedVersion, $this->composerWebsite); - $this->javaStatus = $this->verifyVersion('Java', $this->javaVersion, $this->javaSupportedVersion, $this->javaWebsite); - $this->allureCliStatus = $this->verifyVersion('Allure CLI', $this->allureCliVersion, $this->allureCliSupportedVersion, $this->allureCliWebsite); - $this->seleniumStatus = $this->verifyVersion('Selenium Standalone Server', $this->seleniumVersion, $this->seleniumSupportedVersion, $this->seleniumWebsite); - $this->chromeDriverStatus = $this->verifyVersion('ChromeDriver', $this->chromeDriverVersion, $this->chromeDriverSupportedVersion, $this->chromeDriverWebsite); - $this->geckoDriverStatus = $this->verifyVersion('GeckoDriver', $this->geckoDriverVersion, $this->geckoDriverSupportedVersion, $this->geckoDriverWebsite); - $this->phantomJsStatus = $this->verifyVersion('PhantomJS', $this->phantomJsVersion, $this->phantomJsSupportedVersion, $this->phantomJsWebsite); - - ECHO "\n"; - $mask = "|%-13.13s |%18.18s |%18.18s |%-23.23s |\n"; - printf("---------------------------------------------------------------------------------\n"); - printf($mask, ' Software', 'Supported Version', 'Installed Version', ' Status'); - printf("---------------------------------------------------------------------------------\n"); - printf($mask, ' PHP', $this->phpSupportedVersion . '+', $this->phpVersion, ' ' . $this->phpStatus); - printf($mask, ' Composer', $this->composerSupportedVersion . '+', $this->composerVersion, ' ' . $this->composerStatus); - printf($mask, ' Java', $this->javaSupportedVersion . '+', $this->javaVersion, ' ' . $this->javaStatus); - printf($mask, ' Allure CLI', $this->allureCliSupportedVersion . '+', $this->allureCliVersion, ' ' . $this->allureCliStatus); - printf($mask, ' Selenium', $this->seleniumSupportedVersion . '+', $this->seleniumVersion, ' ' . $this->seleniumStatus); - printf($mask, ' ChromeDriver', $this->chromeDriverSupportedVersion . '+', $this->chromeDriverVersion, ' ' . $this->chromeDriverStatus); - printf($mask, ' GeckoDriver', $this->geckoDriverSupportedVersion . '+', $this->geckoDriverVersion, ' ' . $this->geckoDriverStatus); - printf($mask, ' PhantomJS', $this->phantomJsSupportedVersion . '+', $this->phantomJsVersion, ' ' . $this->phantomJsStatus); - printf("---------------------------------------------------------------------------------\n"); - } - - /** - * Ask if they installed the Browser Drivers via Brew. - * Brew installs things globally making them easier for us to access. - */ - public function didYouInstallViaBrew() - { - ECHO "Did you install Selenium Server, ChromeDriver, GeckoDriver and PhantomJS using Brew? (y/n) "; - $handle1 = fopen ("php://stdin","r"); - $line1 = fgets($handle1); - if (trim($line1) != 'y') { - ECHO "Where did you save the files? (ex /Users/first_last/Automation/) "; - $handle2 = fopen ("php://stdin","r"); - $this->filePath = fgets($handle2); - fclose($handle2); - - ECHO "Which selenium-server-standalone-X.X.X.jar file did you download? (ex 3.6.0) "; - $handle3 = fopen ("php://stdin","r"); - $this->seleniumJarVersion = fgets($handle3); - fclose($handle3); - fclose($handle1); - ECHO "\n"; - } else { - $this->installedViaBrew = true; - fclose($handle1); - ECHO "\n"; - } - } - - /** - * Parse the string that is returned for the Version number only. - * - * @param $stdout - * @return null - */ - public function parseVersion($stdout) - { - preg_match("/\d+(?:\.\d+)+/", $stdout, $matches); - - if (!is_null($matches) && isset($matches[0])) { - return $matches[0]; - } else { - return null; - } - } - - /** - * Parse the string that is returned for the Version number only. - * The message Java returns differs from the others hence the separate function. - * - * @param $stdout - * @return null - */ - public function parseJavaVersion($stdout) - { - preg_match('/\"(.+?)\"/', $stdout, $output_array); - - if (!is_null($output_array)) { - return $output_array[1]; - } else { - return null; - } - } - - /** - * Get the Selenium Server version based on how it was installed. - * - * @return string - */ - public function getSeleniumVersion() - { - $this->installedViaBrew; - $this->filePath; - $this->seleniumJarVersion; - - if ($this->installedViaBrew) { - return shell_exec('selenium-server --version'); - } else { - $command = sprintf('java -jar %s/selenium-server-standalone-%s.jar --version', $this->filePath, $this->seleniumJarVersion); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Get the ChromeDriver version based on how it was installed. - * - * @return string - */ - public function getChromeDriverVersion() - { - $this->installedViaBrew; - $this->filePath; - - if ($this->installedViaBrew) { - return shell_exec('chromedriver --version'); - } else { - $command = sprintf('%s/chromedriver --version', $this->filePath); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Get the PhantomJS version based on how it was installed. - * - * @return string - */ - public function getPhantomJsVersion() - { - $this->installedViaBrew; - $this->filePath; - - if ($this->installedViaBrew) { - return shell_exec('phantomjs --version'); - } else { - $command = sprintf('%s/phantomjs --version', $this->filePath); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Print a "Valid Version Detected" message in color. - * - * @param $softwareName - */ - public function printValidVersion($softwareName) - { - $colors = new CliColors(); - $string = sprintf("%s detected. Version is supported!", $softwareName); - ECHO $colors->getColoredString($string, "black", "green") . "\n"; - } - - /** - * Print a "Upgraded Version Needed" message in color. - * - * @param $softwareName - * @param $supportedVersion - * @param $website - */ - public function printUpgradeVersion($softwareName, $supportedVersion, $website) - { - $colors = new CliColors(); - $string = sprintf("Unsupported version of %s detected. Please upgrade to v%s+: %s", $softwareName, $supportedVersion, $website); - ECHO $colors->getColoredString($string, "black", "yellow") . "\n"; - } - - /** - * Print a "Not Installed. Install Required." message in color. - * - * @param $softwareName - * @param $supportedVersion - * @param $website - */ - public function printNoInstalledVersion($softwareName, $supportedVersion, $website) - { - $colors = new CliColors(); - $string = sprintf("%s not detected. Please install v%s+: %s", $softwareName, $supportedVersion, $website); - ECHO $colors->getColoredString($string, "black", "red") . "\n"; - } - - /** - * Verify that the versions. - * Print the correct status message. - * - * @param $softwareName - * @param $installedVersion - * @param $supportedVersion - * @param $website - * @return string - */ - public function verifyVersion($softwareName, $installedVersion, $supportedVersion, $website) - { - if (is_null($installedVersion)) { - $this->printNoInstalledVersion($softwareName, $supportedVersion, $website); - return 'Installation Required!'; - } else if ($installedVersion >= $supportedVersion) { - $this->printValidVersion($softwareName); - return 'Correct Version!'; - } else { - $this->printUpgradeVersion($softwareName, $supportedVersion, $website); - return 'Upgrade Required!'; - } - } -} - -$preCheck = new PreInstallCheck(); -// @codingStandardsIgnoreEnd \ No newline at end of file diff --git a/dev/tests/acceptance/tests/_bootstrap.php b/dev/tests/acceptance/tests/_bootstrap.php index d15481d18098c..2fa2da03d8f8b 100644 --- a/dev/tests/acceptance/tests/_bootstrap.php +++ b/dev/tests/acceptance/tests/_bootstrap.php @@ -4,107 +4,6 @@ * See COPYING.txt for license details. */ -$bootstrapRoot = dirname(__DIR__); -$projectRootPath = $bootstrapRoot; - -// If we're under a vendor directory, find project root. -if (strpos($bootstrapRoot, '/vendor') !== false) { - $projectRootPath = substr($bootstrapRoot, 0, strpos($bootstrapRoot, '/vendor/')); -} - -define('PROJECT_ROOT', $projectRootPath); - - -//Load base paths from composer autoload file -$autoloadFile = PROJECT_ROOT . '/vendor/autoload.php'; - -$loader = require $autoloadFile; - -// Package Names. -$FW_PACKAGE_NAME = "Magento\FunctionalTestingFramework\\"; -$TESTS_PACKAGE_NAME = "TODO"; -$MAGENTO_PACKAGE_NAME = "Magento\\"; - -// Find framework path -$COMPOSER_FW_FULL_PREFIX = $loader->getPrefixesPsr4()[$FW_PACKAGE_NAME][0] ?? null; -if ($COMPOSER_FW_FULL_PREFIX === null) { - throw new Exception( - "You must have the magento/magento2-functional-testing-framework - installed to be able to generate tests." - ); -} -$FW_PATH = substr( - $COMPOSER_FW_FULL_PREFIX, - 0, - strpos($COMPOSER_FW_FULL_PREFIX, "/src/Magento/FunctionalTestingFramework") -); - -// Find tests path -$COMPOSER_TEST_FULL_PREFIX = $loader->getPrefixesPsr4()[$TESTS_PACKAGE_NAME][0] ?? null; -if ($COMPOSER_TEST_FULL_PREFIX === null) { - $TEST_PATH = __DIR__ . "/functional"; -} else { - // Can't determine what to trim; we don't know the package name/structure yet - $TEST_PATH = $COMPOSER_TEST_FULL_PREFIX; -} - -// We register "Magento\\" to "tests/functional/Magento" for our own class loading, need to try and find a -// prefix that isn't that one. -$COMPOSER_MAGENTO_PREFIXES = $loader->getPrefixesPsr4()[$MAGENTO_PACKAGE_NAME]; -$COMPOSER_MAGENTO_FULL_PREFIX = null; -foreach ($COMPOSER_MAGENTO_PREFIXES as $path) { - if (strpos($path, "tests/functional/Magento") === 0) { - $COMPOSER_MAGENTO_FULL_PREFIX = $path; - } -} -if ($COMPOSER_MAGENTO_FULL_PREFIX === null) { - $MAGENTO_PATH = dirname(__DIR__ . "/../../../../../"); -} else { - $MAGENTO_PATH = substr( - $COMPOSER_MAGENTO_FULL_PREFIX, - 0, - strpos($COMPOSER_MAGENTO_FULL_PREFIX, "/app/code/Magento") - ); -} - -$RELATIVE_TESTS_MODULE_PATH = '/Magento/FunctionalTest'; - -defined('MAGENTO_BP') || define('MAGENTO_BP', realpath($MAGENTO_PATH)); - -//Load constants from .env file -if (file_exists(MAGENTO_BP . '/dev/tests/acceptance/.env')) { - $env = new \Dotenv\Loader(MAGENTO_BP . '/dev/tests/acceptance/.env'); - $env->load(); - - if (array_key_exists('TESTS_MODULE_PATH', $_ENV) xor array_key_exists('TESTS_BP', $_ENV)) { - throw new Exception( - 'You must define both parameters TESTS_BP and TESTS_MODULE_PATH or neither parameter' - ); - } - - foreach ($_ENV as $key => $var) { - defined($key) || define($key, $var); - } - - defined('MAGENTO_CLI_COMMAND_PATH') || define( - 'MAGENTO_CLI_COMMAND_PATH', - 'dev/tests/acceptance/utils/command.php' - ); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PATH', MAGENTO_CLI_COMMAND_PATH); - - defined('MAGENTO_CLI_COMMAND_PARAMETER') || define('MAGENTO_CLI_COMMAND_PARAMETER', 'command'); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PARAMETER', MAGENTO_CLI_COMMAND_PARAMETER); -} - -defined('FW_BP') || define('FW_BP', realpath($FW_PATH)); -defined('TESTS_BP') || define('TESTS_BP', realpath($TEST_PATH)); -defined('TESTS_MODULE_PATH') || define( - 'TESTS_MODULE_PATH', - realpath($TEST_PATH . $RELATIVE_TESTS_MODULE_PATH) -); - -// add the debug flag here -$debug_mode = $_ENV['MFTF_DEBUG'] ?? false; -if (!(bool)$debug_mode && extension_loaded('xdebug')) { - xdebug_disable(); -} +//TODO remove this file once MFTF is fully decoupled from Magento +// Need to load in the root level autload file +require_once realpath(dirname(__DIR__) . "/../../../vendor/autoload.php"); diff --git a/dev/tests/acceptance/tests/_data/lorem_ipsum.docx b/dev/tests/acceptance/tests/_data/lorem_ipsum.docx new file mode 100644 index 0000000000000..488f64e86b6ff Binary files /dev/null and b/dev/tests/acceptance/tests/_data/lorem_ipsum.docx differ diff --git a/dev/tests/acceptance/tests/_data/lorem_ipsum.txt b/dev/tests/acceptance/tests/_data/lorem_ipsum.txt new file mode 100644 index 0000000000000..64cb8d023361a --- /dev/null +++ b/dev/tests/acceptance/tests/_data/lorem_ipsum.txt @@ -0,0 +1,9 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc venenatis cursus eros, eu congue risus eleifend ut. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquet mi diam, at consequat ex imperdiet eu. Nullam vulputate sollicitudin libero, tristique ullamcorper ante pretium eget. Nunc vehicula, risus ut hendrerit ornare, tortor est mattis urna, vitae egestas arcu tellus quis est. Sed mollis est sem. Aenean rhoncus ultricies sapien, id tempus elit lobortis ac. Pellentesque condimentum gravida purus a pretium. Nulla sed sapien mattis, auctor lacus quis, volutpat metus. Nunc mattis diam elit, viverra tincidunt nisl faucibus eu. Duis ac nisl tellus. Aenean eros lectus, malesuada in ex non, pharetra aliquam odio. Aenean ultricies pharetra mauris, ac rutrum quam posuere at. Phasellus et tempor turpis, ut sodales turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. + +Sed fringilla orci at elit gravida, posuere varius elit eleifend. Nam libero dui, rutrum ac massa et, dictum egestas massa. Fusce rutrum, neque vitae vestibulum mattis, magna orci dictum turpis, ut laoreet eros urna lacinia ipsum. Donec eget ultrices eros. Duis sollicitudin ante est. Maecenas semper pellentesque scelerisque. Vestibulum eu venenatis tellus. Etiam nec massa sem. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam blandit molestie justo, non euismod dolor aliquet ac. Duis consectetur enim in arcu suscipit, in tempus nisi commodo. + +Donec sed venenatis nunc. Proin velit leo, porta eget erat elementum, dapibus posuere odio. Nam varius lectus eu cursus tristique. Ut tempus libero vehicula, iaculis augue sit amet, vestibulum justo. Vivamus porta diam vitae malesuada vestibulum. Donec mi dolor, semper at rutrum eget, vehicula non orci. Nunc dolor urna, laoreet et egestas vitae, sodales quis ipsum. Vivamus aliquet viverra enim cursus tincidunt. Pellentesque vel laoreet mi. Aenean ut rhoncus orci. Donec a purus venenatis, tempor dolor in, facilisis ex. Sed nec metus convallis, viverra nisl nec, luctus arcu. Integer blandit arcu a est posuere pharetra. + +Aliquam ultricies lectus ac mauris luctus, a viverra neque rhoncus. Vestibulum id velit eu nisl efficitur lobortis. Sed id metus at ipsum imperdiet porta. Quisque in quam in turpis fermentum condimentum. Phasellus sagittis risus eu tempus scelerisque. Vivamus dapibus sem odio, vitae fermentum sem viverra et. Quisque sit amet cursus neque, vel hendrerit risus. Integer ut diam porta, volutpat risus in, iaculis diam. Suspendisse vulputate non quam et finibus. + +Donec blandit, sem ut posuere dignissim, dolor lorem egestas magna, vel faucibus dui metus eget orci. Nullam ipsum lacus, imperdiet at nisl sed, condimentum dignissim lectus. Nunc orci libero, tincidunt a molestie vel, dapibus at mi. Quisque scelerisque sem quis massa suscipit, sit amet suscipit arcu volutpat. In tincidunt lacus in porttitor mattis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Morbi rutrum gravida orci quis porta. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml b/dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml new file mode 100644 index 0000000000000..65c2bb7004503 --- /dev/null +++ b/dev/tests/acceptance/tests/_suite/WYSIWYGDisabledSuite.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd"> + <suite name="WYSIWYGDisabledSuite"> + <before> + <magentoCLI stepKey="disableWYSYWYG" command="config:set cms/wysiwyg/enabled disabled" /> + </before> + <include> + <group name="WYSIWYGDisabled"/> + </include> + <exclude> + <group name="skip"/> + </exclude> + <after> + <magentoCLI stepKey="disableWYSYWYG" command="config:set cms/wysiwyg/enabled enabled" /> + </after> + </suite> +</suites> \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional.suite.dist.yml b/dev/tests/acceptance/tests/functional.suite.dist.yml deleted file mode 100644 index e01c46ea7c649..0000000000000 --- a/dev/tests/acceptance/tests/functional.suite.dist.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. - -# Codeception Test Suite Configuration -# -# Suite for acceptance tests. -# Perform tests in browser using the WebDriver or PhpBrowser. -# If you need both WebDriver and PHPBrowser tests - create a separate suite. - -class_name: AcceptanceTester -namespace: Magento\FunctionalTestingFramework -modules: - enabled: - - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver - - \Magento\FunctionalTestingFramework\Helper\Acceptance - - \Magento\FunctionalTestingFramework\Helper\MagentoFakerData - - \Magento\FunctionalTestingFramework\Module\MagentoRestDriver: - url: "%MAGENTO_BASE_URL%/rest/default/V1/" - username: "%MAGENTO_ADMIN_USERNAME%" - password: "%MAGENTO_ADMIN_PASSWORD%" - depends: PhpBrowser - part: Json - - \Magento\FunctionalTestingFramework\Module\MagentoSequence - - \Magento\FunctionalTestingFramework\Module\MagentoAssert - - Asserts - config: - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: - url: "%MAGENTO_BASE_URL%" - backend_name: "%MAGENTO_BACKEND_NAME%" - browser: 'chrome' - window_size: maximize - username: "%MAGENTO_ADMIN_USERNAME%" - password: "%MAGENTO_ADMIN_PASSWORD%" - pageload_timeout: 30 - host: %SELENIUM_HOST% - port: %SELENIUM_PORT% - protocol: %SELENIUM_PROTOCOL% - path: %SELENIUM_PATH% - capabilities: - chromeOptions: - args: ["--start-maximized", "--disable-extensions", "--enable-automation"] - diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/composer.json deleted file mode 100644 index df8598a93a2d8..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-configurable-product-catalog-search", - "description": "Magento 2 Functional Test Module Configurable Product Catalog Search", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "magento/magento2-functional-test-module-catalog": "100.0.0-dev", - "magento/magento2-functional-test-module-catalog-search": "100.0.0-dev", - "magento/magento2-functional-test-module-configurable-product": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\ConfigurableProductCatalogSearch\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/composer.json deleted file mode 100644 index e7f61c1bc660d..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-configurable-product-wishlist", - "description": "Magento 2 Functional Test Module Configurable Product Wishlist", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "magento/magento2-functional-test-module-catalog": "100.0.0-dev", - "magento/magento2-functional-test-module-configurable-product": "100.0.0-dev", - "magento/magento2-functional-test-module-wishlist": "100.0.0-dev", - "php": "~7.1.3||~7.2.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\ConfigurableProductWishlist\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/composer.json deleted file mode 100644 index 3a00a525a5478..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "magento/magento2-functional-test-framework", - "description": "Magento 2 Functional Test Framework", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "~2.0.0", - "php": "~7.1.3||~7.2.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\Framework\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json deleted file mode 100644 index e0ceb5bd23c1c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-sample-templates", - "description": "Magento 2 Functional Test Module Sample Templates", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "php": "~7.1.3||~7.2.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\SampleTemplates\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/SampleTemplates" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml index d7a03d4f0198f..4fead0e51497e 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml @@ -16,7 +16,9 @@ <description value=""/> <severity value="CRITICAL"/> <testCaseId value="#"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <before> <createData entity="SamplePerson" stepKey="beforeData"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml index e1e0602d6103d..200c52cab797d 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml @@ -11,7 +11,9 @@ <test name="AssertsTest"> <annotations> <features value="SampleTests"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <createData entity="Simple_US_Customer" stepKey="createData2"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml index cf0ea0225a4b1..d759d1ab27498 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml @@ -12,7 +12,9 @@ <annotations> <features value="SampleTests"/> <stories value="Create a Configurable Product By API"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <before> <createData stepKey="categoryHandle" entity="SimpleSubCategory" /> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml index 8e9336dd6f149..8cd83fd62067f 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml @@ -12,7 +12,9 @@ <annotations> <features value="SampleTests"/> <stories value="Create a Sales Rule By API"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <before> <createData stepKey="saleRule" entity="SimpleSalesRule" /> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml index b809ab89cba52..6a196c2c32c02 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml @@ -11,7 +11,9 @@ <test name="PersistMultipleEntitiesTest"> <annotations> <features value="SampleTests"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <before> <createData entity="simplesubcategory" stepKey="simplecategory"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml index 1bc6bd1865f7c..f908c5cb4572e 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml @@ -11,7 +11,9 @@ <test name="SetPaypalConfigurationTest"> <annotations> <features value="SampleTests"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <createData entity="SamplePaypalConfig" stepKey="createSamplePaypalConfig"/> <createData entity="DefaultPayPalConfig" stepKey="restoreDefaultPaypalConfig"/> @@ -19,7 +21,9 @@ <test name="SetBraintreeConfigurationTest"> <annotations> <features value="SampleTests"/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <createData entity="SampleBraintreeConfig" stepKey="createSampleBraintreeConfig"/> <createData entity="DefaultBraintreeConfig" stepKey="restoreDefaultBraintreeConfig"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml index fbf203834b45e..b18c24050d3a6 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml @@ -11,7 +11,9 @@ <annotations> <features value="SampleTests"/> <stories value="Update simple product by api test."/> - <group value="skip"/> + <skip> + <issueId value="SAMPLE"/> + </skip> </annotations> <before> <createData stepKey="categoryHandle" entity="SimpleSubCategory"/> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json deleted file mode 100644 index dc91e593e745b..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-sample-tests", - "description": "Magento 2 Functional Test Module Sample Tests", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "php": "~7.1.3||~7.2.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\SampleTests\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/SampleTests" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/_bootstrap.php b/dev/tests/acceptance/tests/functional/_bootstrap.php deleted file mode 100644 index ac7a13ea41d29..0000000000000 --- a/dev/tests/acceptance/tests/functional/_bootstrap.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -// Here you can initialize variables that will be available to your tests -require_once dirname(__DIR__) . '/_bootstrap.php'; diff --git a/dev/tests/acceptance/utils/command.php b/dev/tests/acceptance/utils/command.php deleted file mode 100644 index 943e2e9776af4..0000000000000 --- a/dev/tests/acceptance/utils/command.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -if (isset($_POST['command'])) { - $command = urldecode($_POST['command']); - $php = PHP_BINARY ?: (PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'); - $valid = validateCommand($command); - if ($valid) { - exec(escapeCommand($php . ' -f ../../../../bin/magento ' . $command) . " 2>&1", $output, $exitCode); - if ($exitCode == 0) { - http_response_code(202); - } else { - http_response_code(500); - } - echo implode("\n", $output); - } else { - http_response_code(403); - echo "Given command not found valid in Magento CLI Command list."; - } -} else { - http_response_code(412); - echo("Command parameter is not set."); -} - -/** - * Returns escaped command. - * - * @param string $command - * @return string - */ -function escapeCommand($command) -{ - $escapeExceptions = [ - '> /dev/null &' => '--dev-null-amp--' - ]; - - $command = escapeshellcmd( - str_replace(array_keys($escapeExceptions), array_values($escapeExceptions), $command) - ); - - return str_replace(array_values($escapeExceptions), array_keys($escapeExceptions), $command); -} - -/** - * Checks magento list of CLI commands for given $command. Does not check command parameters, just base command. - * @param string $command - * @return bool - */ -function validateCommand($command) -{ - $php = PHP_BINARY ?: (PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'); - exec($php . ' -f ../../../../bin/magento list', $commandList); - // Trim list of commands after first whitespace - $commandList = array_map("trimAfterWhitespace", $commandList); - return in_array(trimAfterWhitespace($command), $commandList); -} - -/** - * Returns given string trimmed of everything after the first found whitespace. - * @param string $string - * @return string - */ -function trimAfterWhitespace($string) -{ - return strtok($string, ' '); -} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls index 3466db5c71f6a..7eb175a88e322 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls @@ -5,7 +5,16 @@ type Query { testItem(id: Int!) : Item @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\Item") } +type Mutation { + testItem(id: Int!) : MutationItem @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\Item") +} + type Item { item_id: Int name: String } + +type MutationItem { + item_id: Int + name: String +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQueryExtension/etc/schema.graphqls b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQueryExtension/etc/schema.graphqls index dc01d993c3818..b970ad8376349 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQueryExtension/etc/schema.graphqls +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQueryExtension/etc/schema.graphqls @@ -4,3 +4,7 @@ type Item { integer_list: [Int] @resolver(class: "Magento\\TestModuleGraphQlQueryExtension\\Model\\Resolver\\IntegerList") } + +type MutationItem { + integer_list: [Int] @resolver(class: "Magento\\TestModuleGraphQlQueryExtension\\Model\\Resolver\\IntegerList") +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index af01e455fdd9d..cfcd5dd1b51dd 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -34,6 +34,7 @@ abstract class GraphQlAbstract extends WebapiAbstract * @param array $variables * @param string $operationName * @return array|int|string|float|bool GraphQL call results + * @throws \Exception */ public function graphQlQuery( string $query, @@ -92,9 +93,37 @@ private function getAppCache() private function getGraphQlClient() { if ($this->graphQlClient === null) { - return Bootstrap::getObjectManager()->get(\Magento\TestFramework\TestCase\GraphQl\Client::class); - } else { - $this->graphQlClient; + $this->graphQlClient = Bootstrap::getObjectManager()->get( + \Magento\TestFramework\TestCase\GraphQl\Client::class + ); + } + return $this->graphQlClient; + } + + /** + * Compare actual response fields with expected + * + * @param array $actualResponse + * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] + * OR [['response_field' => $field, 'expected_value' => $value], ...] + */ + protected function assertResponseFields($actualResponse, $assertionMap) + { + foreach ($assertionMap as $key => $assertionData) { + $expectedValue = isset($assertionData['expected_value']) + ? $assertionData['expected_value'] + : $assertionData; + $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; + self::assertNotNull( + $expectedValue, + "Value of '{$responseField}' field must not be NULL" + ); + self::assertEquals( + $expectedValue, + $actualResponse[$responseField], + "Value of '{$responseField}' field in response does not match expected value: " + . var_export($expectedValue, true) + ); } } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php index aee77ae6d589f..e2e32c119af21 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php @@ -63,7 +63,7 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat } /** - * Get proper SOAP client instance that is initialized with with WSDL corresponding to requested service interface. + * Get proper SOAP client instance that is initialized with WSDL corresponding to requested service interface. * * @param string $serviceInfo PHP service interface name, should include version if present * @param string|null $storeCode diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php index 5241e281b342d..386bd9fc9aeeb 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php @@ -23,6 +23,7 @@ class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\Web /** * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php + * @return void */ public function testGet() { @@ -35,6 +36,9 @@ public function testGet() $this->assertEquals($attributeCode, $attribute['attribute_code']); } + /** + * @return void + */ public function testGetList() { $searchCriteria = [ @@ -83,6 +87,7 @@ public function testGetList() /** * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void */ public function testCreate() { @@ -101,7 +106,8 @@ public function testCreate() "is_filterable_in_search" => true, ]; - $this->assertEquals('front_lbl', $attribute['default_frontend_label']); + $this->assertEquals('default_label', $attribute['default_frontend_label']); + $this->assertEquals('front_lbl_store1', $attribute['frontend_labels'][0]['label']); foreach ($expectedData as $key => $value) { $this->assertEquals($value, $attribute[$key]); } @@ -116,6 +122,7 @@ public function testCreate() /** * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php + * @return void */ public function testCreateWithExceptionIfAttributeAlreadyExists() { @@ -132,6 +139,7 @@ public function testCreateWithExceptionIfAttributeAlreadyExists() /** * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void */ public function testUpdate() { @@ -146,9 +154,12 @@ public function testUpdate() 'attribute_code' => $attributeCode, 'entity_type_id' => 4, 'is_used_in_grid' => true, + //Update existing 'default_frontend_label' => 'default_label_new', 'frontend_labels' => [ - ['store_id' => 1, 'label' => 'front_lbl_new'], + //Update existing + ['store_id' => 0, 'label' => 'front_lbl_store0_new'], + ['store_id' => 1, 'label' => 'front_lbl_store1_new'], ], "options" => [ //Update existing @@ -190,6 +201,7 @@ public function testUpdate() $this->assertEquals(true, $result['is_used_in_grid']); $this->assertEquals($attributeCode, $result['attribute_code']); $this->assertEquals('default_label_new', $result['default_frontend_label']); + $this->assertEquals('front_lbl_store1_new', $result['frontend_labels'][0]['label']); //New option set as default $this->assertEquals($result['options'][3]['value'], $result['default_value']); $this->assertEquals("Default Blue Updated", $result['options'][1]['label']); @@ -197,6 +209,72 @@ public function testUpdate() /** * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void + */ + public function testUpdateWithNoDefaultLabelAndAdminStorelabel() + { + $attributeCode = uniqid('label_attr_code'); + $attribute = $this->createAttribute($attributeCode); + + $attributeData = [ + 'attribute' => [ + 'attribute_id' => $attribute['attribute_id'], + 'attribute_code' => $attributeCode, + 'entity_type_id' => 4, + 'is_used_in_grid' => true, + 'frontend_labels' => [ + //Update existing + ['store_id' => 0, 'label' => 'front_lbl_store0_new'], + ['store_id' => 1, 'label' => 'front_lbl_store1_new'], + ], + 'is_required' => false, + 'frontend_input' => 'select', + ], + ]; + $result = $this->updateAttribute($attributeCode, $attributeData); + + $this->assertEquals($attribute['attribute_id'], $result['attribute_id']); + $this->assertEquals(true, $result['is_used_in_grid']); + $this->assertEquals($attributeCode, $result['attribute_code']); + $this->assertEquals('front_lbl_store0_new', $result['default_frontend_label']); + $this->assertEquals('front_lbl_store1_new', $result['frontend_labels'][0]['label']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void + */ + public function testUpdateWithNoDefaultLabelAndNoAdminStoreLabel() + { + $attributeCode = uniqid('label_attr_code'); + $attribute = $this->createAttribute($attributeCode); + + $attributeData = [ + 'attribute' => [ + 'attribute_id' => $attribute['attribute_id'], + 'attribute_code' => $attributeCode, + 'entity_type_id' => 4, + 'is_used_in_grid' => true, + 'frontend_labels' => [ + //Update existing + ['store_id' => 1, 'label' => 'front_lbl_store1_new'], + ], + 'is_required' => false, + 'frontend_input' => 'select', + ], + ]; + $result = $this->updateAttribute($attributeCode, $attributeData); + + $this->assertEquals($attribute['attribute_id'], $result['attribute_id']); + $this->assertEquals(true, $result['is_used_in_grid']); + $this->assertEquals($attributeCode, $result['attribute_code']); + $this->assertEquals('default_label', $result['default_frontend_label']); + $this->assertEquals('front_lbl_store1_new', $result['frontend_labels'][0]['label']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php + * @return void */ public function testUpdateWithNewOption() { @@ -234,6 +312,7 @@ public function testUpdateWithNewOption() /** * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php + * @return void */ public function testDeleteById() { @@ -241,6 +320,9 @@ public function testDeleteById() $this->assertTrue($this->deleteAttribute($attributeCode)); } + /** + * @return void + */ public function testDeleteNoSuchEntityException() { $attributeCode = 'some_test_code'; @@ -286,11 +368,10 @@ protected function createAttribute($attributeCode) 'attribute' => [ 'attribute_code' => $attributeCode, 'entity_type_id' => '4', + "default_frontend_label" => 'default_label', 'frontend_labels' => [ - [ - 'store_id' => 0, - 'label' => 'front_lbl' - ], + ['store_id' => 0, 'label' => 'front_lbl_store0'], + ['store_id' => 1, 'label' => 'front_lbl_store1'], ], 'is_required' => true, "default_value" => "", @@ -423,6 +504,9 @@ protected function updateAttribute($attributeCode, $attributeData) return $this->_webApiCall($serviceInfo, $attributeData); } + /** + * @inheritdoc + */ protected function tearDown() { foreach ($this->createdAttributes as $attributeCode) { diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php index 1cd9a5c2b9c86..e83811042fd8b 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php @@ -208,7 +208,7 @@ public function testAddNegative($optionData) ]; if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { - if (isset($optionDataPost['title']) && empty($optionDataPost['title'])) { + if ($optionDataPost['title'] === null || $optionDataPost['title'] === '') { $this->expectException('SoapFault'); $this->expectExceptionMessage('Missed values for option required fields'); } else { diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRenderListInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRenderListInterfaceTest.php index 807269c03c06f..fb3ff3b134081 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRenderListInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRenderListInterfaceTest.php @@ -25,7 +25,6 @@ class ProductRenderListInterfaceTest extends WebapiAbstract * @magentoApiDataFixture Magento/Catalog/_files/product_special_price.php * @dataProvider productRenderInfoProvider * @param array $expectedRenderInfo - * @param string $ids * @return void */ public function testGetList(array $expectedRenderInfo) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index e140305db4dcd..5f276cfcf074c 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -3,16 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Api; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Downloadable\Model\Link; use Magento\Store\Model\Store; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Store\Model\Website; use Magento\Store\Model\WebsiteRepository; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Api\SortOrder; @@ -696,6 +699,31 @@ public function testUpdate() $this->assertEquals($productData[ProductInterface::SKU], $response[ProductInterface::SKU]); } + /** + * Update product with extension attributes. + * + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + */ + public function testUpdateWithExtensionAttributes(): void + { + $sku = 'downloadable-product'; + $linksKey = 'downloadable_product_links'; + $productData = [ + ProductInterface::NAME => 'Downloadable (updated)', + ProductInterface::SKU => $sku, + ]; + $response = $this->updateProduct($productData); + + self::assertArrayHasKey(ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY, $response); + self::assertArrayHasKey($linksKey, $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]); + self::assertCount(1, $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY][$linksKey]); + + $linkData = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY][$linksKey][0]; + + self::assertArrayHasKey(Link::KEY_LINK_URL, $linkData); + self::assertEquals('http://example.com/downloadable.txt', $linkData[Link::KEY_LINK_URL]); + } + /** * @param array $product * @return array|bool|float|int|string diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php index 144f3a9926fe3..8a00de1be094f 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php @@ -10,7 +10,7 @@ 'type' => 'field', 'sort_order' => 1, 'is_require' => 1, - 'price' => 10, + 'price' => -10, 'price_type' => 'fixed', 'sku' => 'sku1', 'max_characters' => 10, diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php index 5d2737b3aa532..f8890ca2eaac0 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php @@ -6,7 +6,7 @@ return [ 'empty_required_field' => [ - 'title' => '', + 'title' => null, 'type' => 'field', 'sort_order' => 1, 'is_require' => 1, @@ -15,17 +15,6 @@ 'sku' => 'sku1', 'max_characters' => 10, ], - 'negative_price' => [ - 'title' => 'area option', - 'type' => 'area', - 'sort_order' => 2, - 'is_require' => 0, - 'price' => -20, - 'price_type' => 'percent', - 'sku' => 'sku2', - 'max_characters' => 20, - - ], 'negative_value_of_image_size' => [ 'title' => 'file option', 'type' => 'file', @@ -54,7 +43,7 @@ 'price' => 10.0, 'price_type' => 'fixed', 'sku' => 'radio option 1 sku', - 'title' => '', + 'title' => null, 'sort_order' => 1, ], ], diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php index 8c30ef875c288..b7db294eedfbe 100644 --- a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php @@ -117,7 +117,6 @@ public function testSave() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php index 4b821b118f999..d4df57fbff89e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php @@ -144,7 +144,6 @@ public function testSave() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); @@ -193,7 +192,6 @@ public function testSaveForMyCart() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php index 8ccd9d0c94f61..0621370972763 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductViewTest.php @@ -285,31 +285,6 @@ private function assertBundleProductOptions($product, $actualResponse) ); } - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } - /** * @magentoApiDataFixture Magento/Bundle/_files/product_with_multiple_options_1.php */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php new file mode 100644 index 0000000000000..6b57f84e4b9c4 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test of getting child products info of configurable product on category request + */ +class CategoryProductsVariantsTest extends GraphQlAbstract +{ + /** + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testGetSimpleProductsFromCategory() + { + + $query + = <<<QUERY +{ + category(id: 2) { + id + name + products { + items { + sku + ... on ConfigurableProduct { + variants { + product { + sku + } + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple_10', false, null, true); + + $this->assertArrayHasKey('variants', $response['category']['products']['items'][0]); + $this->assertCount(2, $response['category']['products']['items'][0]['variants']); + $this->assertSimpleProductFields($product, $response['category']['products']['items'][0]['variants'][0]); + } + + /** + * @param ProductInterface $product + * @param array $actualResponse + */ + private function assertSimpleProductFields($product, $actualResponse) + { + $assertionMap = [ + [ + 'response_field' => 'product', 'expected_value' => [ + "sku" => $product->getSku() + ] + ], + ]; + + $this->assertResponseFields($actualResponse, $assertionMap); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 0133b87e757bd..8841fab75790c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -8,6 +8,7 @@ namespace Magento\GraphQl\Catalog; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; use Magento\Framework\DataObject; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Catalog\Api\Data\ProductInterface; @@ -63,9 +64,6 @@ public function testCategoriesTree() children { level id - children { - id - } } } } @@ -286,16 +284,13 @@ public function testCategoryProducts() */ public function testAnchorCategory() { - /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ - $categoryCollection = $this->objectManager->create( - \Magento\Catalog\Model\ResourceModel\Category\Collection::class - ); + /** @var CategoryCollection $categoryCollection */ + $categoryCollection = $this->objectManager->create(CategoryCollection::class); $categoryCollection->addFieldToFilter('name', 'Category 1'); + /** @var CategoryInterface $category */ $category = $categoryCollection->getFirstItem(); - /** @var \Magento\Framework\EntityManager\MetadataPool $entityManagerMetadataPool */ - $entityManagerMetadataPool = $this->objectManager->create(\Magento\Framework\EntityManager\MetadataPool::class); - $categoryLinkField = $entityManagerMetadataPool->getMetadata(CategoryInterface::class)->getLinkField(); - $categoryId = $category->getData($categoryLinkField); + $categoryId = $category->getId(); + $this->assertNotEmpty($categoryId, "Preconditions failed: category is not available."); $query = <<<QUERY @@ -419,29 +414,4 @@ private function assertAttributes($actualResponse) $this->assertArrayHasKey($eavAttribute, $actualResponse); } } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - self::assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - self::assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php index 2db400769c300..063da7c11bf7f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php @@ -231,29 +231,4 @@ private function assertAttributeType($attributeTypes, $expectedAttributeCodes, $ ); } } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields(array $actualResponse, array $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index dc5a66fbb34ab..f1a79490b3dcf 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -1283,29 +1283,4 @@ private function assertProductItemsWithMaximalAndMinimalPriceCheck(array $filter ); } } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields(array $actualResponse, array $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index b5aa73a5a7a34..34cebde64d03a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -957,29 +957,4 @@ private function eavAttributesToGraphQlSchemaFieldTranslator(string $eavAttribut } return $eavAttributeCode; } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - self::assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - self::assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/VirtualProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/VirtualProductViewTest.php index df3386f2f7836..58b6d4f0e4ea2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/VirtualProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/VirtualProductViewTest.php @@ -134,29 +134,4 @@ private function assertBaseFields($product, $actualResponse) $this->assertResponseFields($actualResponse, $assertionMap); } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php index 84c4e80d325ab..735ae7fff646b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php @@ -487,31 +487,6 @@ private function assertConfigurableProductOptions($actualResponse) } } - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields(array $actualResponse, array $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } - private function getConfigurableOptions() { if (!empty($this->configurableOptions)) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php index 60fa63338e790..88ce7e91d94bc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php @@ -164,29 +164,4 @@ public function assertCustomerAddressesFields($customer, $actualResponse) $this->assertResponseFields($actualResponse['customer']['addresses'][$addressKey], $assertionMap); } } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php index 57d39f04347bf..a7902db259bc3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/DownloadableProductViewTest.php @@ -261,29 +261,4 @@ private function assertDownloadableProductSamples($product, $actualResponse) ] ); } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php index 841bff42a3057..cbd91f6fbdb37 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/GroupedProduct/GroupedProductViewTest.php @@ -92,29 +92,4 @@ private function assertGroupedProductItems($product, $actualResponse) ); } } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php new file mode 100644 index 0000000000000..4657a1e763ae1 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Store/StoreConfigResolverTest.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Store; + +use Magento\Store\Api\Data\StoreConfigInterface; +use Magento\Store\Api\StoreConfigManagerInterface; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Api\StoreResolverInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the GraphQL endpoint's StoreConfigs query + */ +class StoreConfigResolverTest extends GraphQlAbstract +{ + + /** @var ObjectManager */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Store/_files/store.php + */ + public function testGetStoreConfig() + { + /** @var StoreConfigManagerInterface $storeConfigsManager */ + $storeConfigsManager = $this->objectManager->get(StoreConfigManagerInterface::class); + /** @var StoreResolverInterface $storeResolver */ + $storeResolver = $this->objectManager->get(StoreResolverInterface::class); + /** @var StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->get(StoreRepositoryInterface::class); + $storeId = $storeResolver->getCurrentStoreId(); + $store = $storeRepository->getById($storeId); + /** @var StoreConfigInterface $storeConfig */ + $storeConfig = current($storeConfigsManager->getStoreConfigs([$store->getCode()])); + $query + = <<<QUERY +{ + storeConfig{ + id, + code, + website_id, + locale, + base_currency_code, + default_display_currency_code, + timezone, + weight_unit, + base_url, + base_link_url, + base_static_url, + base_media_url, + secure_base_url, + secure_base_link_url, + secure_base_static_url, + secure_base_media_url + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('storeConfig', $response); + $this->assertEquals($storeConfig->getId(), $response['storeConfig']['id']); + $this->assertEquals($storeConfig->getCode(), $response['storeConfig']['code']); + $this->assertEquals($storeConfig->getLocale(), $response['storeConfig']['locale']); + $this->assertEquals($storeConfig->getBaseCurrencyCode(), $response['storeConfig']['base_currency_code']); + $this->assertEquals( + $storeConfig->getDefaultDisplayCurrencyCode(), + $response['storeConfig']['default_display_currency_code'] + ); + $this->assertEquals($storeConfig->getTimezone(), $response['storeConfig']['timezone']); + $this->assertEquals($storeConfig->getWeightUnit(), $response['storeConfig']['weight_unit']); + $this->assertEquals($storeConfig->getBaseUrl(), $response['storeConfig']['base_url']); + $this->assertEquals($storeConfig->getBaseLinkUrl(), $response['storeConfig']['base_link_url']); + $this->assertEquals($storeConfig->getBaseStaticUrl(), $response['storeConfig']['base_static_url']); + $this->assertEquals($storeConfig->getBaseMediaUrl(), $response['storeConfig']['base_media_url']); + $this->assertEquals($storeConfig->getSecureBaseUrl(), $response['storeConfig']['secure_base_url']); + $this->assertEquals($storeConfig->getSecureBaseLinkUrl(), $response['storeConfig']['secure_base_link_url']); + $this->assertEquals( + $storeConfig->getSecureBaseStaticUrl(), + $response['storeConfig']['secure_base_static_url'] + ); + $this->assertEquals($storeConfig->getSecureBaseMediaUrl(), $response['storeConfig']['secure_base_media_url']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/ProductViewTest.php index 4d0797d32e493..dfbe943ecdcd9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Tax/ProductViewTest.php @@ -325,29 +325,4 @@ private function assertBaseFields($product, $actualResponse) $this->assertResponseFields($actualResponse, $assertionMap); } - - /** - * @param array $actualResponse - * @param array $assertionMap ['response_field_name' => 'response_field_value', ...] - * OR [['response_field' => $field, 'expected_value' => $value], ...] - */ - private function assertResponseFields($actualResponse, $assertionMap) - { - foreach ($assertionMap as $key => $assertionData) { - $expectedValue = isset($assertionData['expected_value']) - ? $assertionData['expected_value'] - : $assertionData; - $responseField = isset($assertionData['response_field']) ? $assertionData['response_field'] : $key; - $this->assertNotNull( - $expectedValue, - "Value of '{$responseField}' field must not be NULL" - ); - $this->assertEquals( - $expectedValue, - $actualResponse[$responseField], - "Value of '{$responseField}' field in response does not match expected value: " - . var_export($expectedValue, true) - ); - } - } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php new file mode 100644 index 0000000000000..b6e1a61f0357c --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\TestModule; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Make sure that it is possible to use GraphQL mutations in Magento + */ +class GraphQlMutationTest extends GraphQlAbstract +{ + public function testMutation() + { + $id = 3; + + $query = <<<MUTATION +mutation { + testItem(id: {$id}) { + item_id, + name, + integer_list + } +} +MUTATION; + + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('testItem', $response); + $testItem = $response['testItem']; + $this->assertArrayHasKey('integer_list', $testItem); + $this->assertEquals([4, 5, 6], $testItem['integer_list']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php index aef162d92b1c1..45bae261bb617 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php @@ -38,7 +38,6 @@ public function testInvoke() $orderCollection = $this->objectManager->get(\Magento\Sales\Model\ResourceModel\Order\Collection::class); $order = $orderCollection->getFirstItem(); -// $order = $this->objectManager->create('Magento\Sales\Model\Order')->loadByIncrementId('100000001'); /** @var \Magento\Sales\Model\Order\Item $orderItem */ $orderItem = current($order->getAllItems()); $items = [ diff --git a/dev/tests/functional/.gitignore b/dev/tests/functional/.gitignore index d47f62b56bb9a..92e3004580b0c 100644 --- a/dev/tests/functional/.gitignore +++ b/dev/tests/functional/.gitignore @@ -6,3 +6,4 @@ /vendor phpunit.xml credentials.xml +.htaccess diff --git a/dev/tests/functional/bootstrap.php b/dev/tests/functional/bootstrap.php index 9967697124e80..04156ca88ea70 100644 --- a/dev/tests/functional/bootstrap.php +++ b/dev/tests/functional/bootstrap.php @@ -6,23 +6,25 @@ defined('MTF_BOOT_FILE') || define('MTF_BOOT_FILE', __FILE__); defined('MTF_BP') || define('MTF_BP', str_replace('\\', '/', (__DIR__))); +defined('BP') || define('BP', str_replace('\\', '/', dirname(dirname(dirname((__DIR__)))))); defined('MTF_TESTS_PATH') || define('MTF_TESTS_PATH', MTF_BP . '/tests/app/'); defined('MTF_STATES_PATH') || define('MTF_STATES_PATH', MTF_BP . '/lib/Magento/Mtf/App/State/'); -require_once __DIR__ . '/../../../app/bootstrap.php'; restore_error_handler(); -$vendorAutoload = __DIR__ . '/vendor/autoload.php'; - -if (isset($composerAutoloader)) { - /** var $mtfComposerAutoload \Composer\Autoload\ClassLoader */ - $mtfComposerAutoload = include $vendorAutoload; - $composerAutoloader->addClassMap($mtfComposerAutoload->getClassMap()); -} else { - $composerAutoloader = include $vendorAutoload; -} - +include __DIR__ . '/vendor/autoload.php'; setCustomErrorHandler(); +/* Custom umask value may be provided in optional mage_umask file in root */ +$umaskFile = BP . '/magento_umask'; +$mask = file_exists($umaskFile) ? octdec(file_get_contents($umaskFile)) : 002; +umask($mask); + +date_default_timezone_set('UTC'); + +/* For data consistency between displaying (printing) and serialization a float number */ +ini_set('precision', 14); +ini_set('serialize_precision', 14); + /** * Set custom error handler */ diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php index 5bb1efee62959..192cab5751986 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php @@ -6,9 +6,6 @@ namespace Magento\Mtf\Util\Generate; -use Magento\Framework\App; -use Magento\Framework\ObjectManagerInterface; - /** * Factory classes generator. * @@ -16,24 +13,10 @@ */ class Factory extends AbstractGenerate { - /** - * @var \Magento\Framework\ObjectManagerInterface - */ - protected $objectManager; - - /** - * @constructor - * @param ObjectManagerInterface $objectManager - */ - public function __construct(ObjectManagerInterface $objectManager) - { - $this->objectManager = $objectManager; - } - /** * Generate Handlers. * - * @return \Magento\Framework\App\ResponseInterface + * @return bool */ public function launch() { @@ -43,7 +26,7 @@ public function launch() $this->objectManager->create(\Magento\Mtf\Util\Generate\Factory\Page::class)->launch(); $this->objectManager->create(\Magento\Mtf\Util\Generate\Factory\Repository::class)->launch(); - return $this->objectManager->get(\Magento\Framework\App\ResponseInterface::class); + return true; } /** diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php b/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php index a4b438a1d2de0..110d9d5fbd6c3 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php @@ -10,37 +10,6 @@ */ class SequenceSorter implements SequenceSorterInterface { - /** - * Magento ObjectManager. - * - * @var \Magento\Framework\ObjectManagerInterface - */ - protected $magentoObjectManager; - - /** - * @constructor - */ - public function __construct() - { - $this->initObjectManager(); - } - - /** - * Initialize Magento ObjectManager. - * - * @return void - */ - protected function initObjectManager() - { - if (!$this->magentoObjectManager) { - $objectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory( - BP, - $_SERVER - ); - $this->magentoObjectManager = $objectManagerFactory->create($_SERVER); - } - } - /** * Get Magento module sequence load. * @@ -48,7 +17,8 @@ protected function initObjectManager() */ protected function getModuleSequence() { - return $this->magentoObjectManager->create(\Magento\Framework\Module\ModuleList\Loader::class)->load(); + $ds = DIRECTORY_SEPARATOR; + return json_decode(file_get_contents(MTF_BP . $ds . 'generated' . $ds . 'moduleSequence.json'), true); } /** diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php index 83f86b264737c..0a8db19afe971 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/FrontendDecorator.php @@ -64,7 +64,7 @@ public function __construct(CurlTransport $transport, Customer $customer) protected function authorize(Customer $customer) { $url = $_ENV['app_frontend_url'] . 'customer/account/login/'; - $this->transport->write($url); + $this->transport->write($url, [], CurlInterface::GET); $this->read(); $url = $_ENV['app_frontend_url'] . 'customer/account/loginPost/'; $data = [ diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php index f028a0bcf65ba..7d75db5f0368c 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php @@ -22,7 +22,7 @@ class AssertHttpUsedOnFrontend extends AbstractConstraint * * @var string */ - private $unsecuredProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTP; + const SCHEME_HTTP = 'http'; /** * Browser interface. @@ -53,11 +53,11 @@ public function processAssert(BrowserInterface $browser, Customer $customer) // Log in to Customer Account on Storefront to assert that http is used indeed. $this->objectManager->create(LogInCustomerOnStorefront::class, ['customer' => $this->customer])->run(); - $this->assertUsedProtocol($this->unsecuredProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTP); // Log out from Customer Account on Storefront to assert that JS is deployed validly as a part of statics. $this->objectManager->create(LogOutCustomerOnStorefront::class)->run(); - $this->assertUsedProtocol($this->unsecuredProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTP); } /** diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php index 5da51c8079d7f..cc64e4a4c7299 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php @@ -16,18 +16,10 @@ class AssertHttpsUsedOnBackend extends AbstractConstraint { /** - * Secured protocol format. - * - * @var string - */ - private $securedProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTPS; - - /** - * Unsecured protocol format. - * - * @var string + * Protocols */ - private $unsecuredProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTP; + const SCHEME_HTTP = 'http'; + const SCHEME_HTTPS = 'https'; /** * Browser interface. @@ -50,7 +42,7 @@ public function processAssert(BrowserInterface $browser, Dashboard $adminDashboa // Open specified Admin page using Navigation Menu to assert that JS is deployed validly as a part of statics. $adminDashboardPage->open()->getMenuBlock()->navigate($navMenuPath); - $this->assertUsedProtocol($this->securedProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTPS); $this->assertDirectHttpUnavailable(); } @@ -80,10 +72,10 @@ protected function assertUsedProtocol($expectedProtocol) */ protected function assertDirectHttpUnavailable() { - $fakeUrl = str_replace($this->securedProtocol, $this->unsecuredProtocol, $this->browser->getUrl()); + $fakeUrl = str_replace(self::SCHEME_HTTPS, self::SCHEME_HTTP, $this->browser->getUrl()); $this->browser->open($fakeUrl); \PHPUnit\Framework\Assert::assertStringStartsWith( - $this->securedProtocol, + self::SCHEME_HTTPS, $this->browser->getUrl(), 'Merchant is not redirected to https if tries to access the Admin panel page directly via http.' ); diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php index 8320514520b03..b238302e8f47f 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Constraint/AssertDeviceDataIsPresentInBraintreeRequest.php @@ -17,7 +17,7 @@ class AssertDeviceDataIsPresentInBraintreeRequest extends AbstractConstraint /** * Log file name. */ - const FILE_NAME = 'debug.log'; + const FILE_NAME = 'payment.log'; /** * Device data pattern for regular expression. diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml index acf15c0e28252..70582077ed29d 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderBackendTest.xml @@ -64,28 +64,5 @@ <constraint name="Magento\Sales\Test\Constraint\AssertCaptureInCommentsHistory" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> </variation> - <variation name="CreateOrderBackendTestBraintreeVariation3" summary="Checkout with Braintree Credit Card from Admin (Basic Fraud Protection)" ticketId="MAGETWO-46470"> - <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> - <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> - <data name="products/1" xsi:type="string">configurableProduct::with_one_option</data> - <data name="products/2" xsi:type="string">bundleProduct::bundle_fixed_100_dollar_product</data> - <data name="customer/dataset" xsi:type="string">default</data> - <data name="taxRule" xsi:type="string">us_ca_ny_rule</data> - <data name="billingAddress/dataset" xsi:type="string">US_address_1_without_email</data> - <data name="saveAddress" xsi:type="string">No</data> - <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> - <data name="shipping/shipping_method" xsi:type="string">Fixed</data> - <data name="prices" xsi:type="array"> - <item name="grandTotal" xsi:type="string">145.98</item> - </data> - <data name="payment/method" xsi:type="string">braintree</data> - <data name="paymentForm" xsi:type="string">braintree</data> - <data name="creditCard/dataset" xsi:type="string">visa_braintree_fraud_rejected</data> - <data name="configData" xsi:type="string">braintree</data> - <data name="status" xsi:type="string">Processing</data> - <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> - </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml index f318287720c38..87618777d7907 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml @@ -11,7 +11,7 @@ <selector>[name="name"]</selector> </name> <parent_category> - <selector>div[data-index="parent"]>div</selector> + <selector>div[data-index="parent"]>div[class="admin__field-control"]</selector> <class>Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Section\ProductDetails\CategoryIds</class> </parent_category> </fields> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php index 9331c2c987549..fa2b66d3e9698 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php @@ -28,7 +28,7 @@ class ProductForm extends FormSections * * @var string */ - protected $attribute = './/*[contains(@class,"label")]/span[text()="%s"]'; + protected $attribute = './/*[contains(@class,"label")]//span[text()="%s"]'; /** * Product new from date field on the product form diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php index 2b72d2b915ee6..990906b7e302a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php @@ -89,7 +89,7 @@ class View extends AbstractConfigureBlock * * @var string */ - protected $productDescription = '.product.attribute.description'; + protected $productDescription = '.product.attribute.description .value'; /** * Product short-description element. diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php index 400289eccda15..e2193b799c3be 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php @@ -70,7 +70,7 @@ private function createProducts(FixtureFactory $fixtureFactory, $productsData) $products[] = $product; } elseif ($this->data === null) { - $this->data = strval($productData); + $this->data = (string)$productData; } } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml index b999900042c37..387e6418ee572 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml @@ -102,7 +102,7 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderButtonsAvailable" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> - <data name="issue" xsi:type="string">MAGETWO-66737: Magento\Checkout\Test\TestCase\OnePageCheckoutTest with OnePageCheckoutTestVariation3 is not stable</data> + <data name="issue" xsi:type="string">MAGETWO-66737: Magento\Checkout\Test\TestCase\OnePageCheckoutTest with OnePageCheckoutTestVariation3 and 4 is not stable</data> </variation> <variation name="OnePageCheckoutTestVariation4" summary="One Page Checkout Products with Special Prices" ticketId="MAGETWO-12429"> <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0</data> @@ -125,6 +125,7 @@ <constraint name="Magento\Shipping\Test\Constraint\AssertShipmentSuccessCreateMessage" /> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal"/> + <data name="issue" xsi:type="string">MAGETWO-66737: Magento\Checkout\Test\TestCase\OnePageCheckoutTest with OnePageCheckoutTestVariation3 and 4 is not stable</data> </variation> <variation name="OnePageCheckoutTestVariation5" summary="Guest Checkout using Check/Money Order and Free Shipping with Prices/Taxes Verifications" ticketId="MAGETWO-12412"> <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0</data> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml index 9e20bbdaac1d9..5c05d4a840009 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutTest.xml @@ -49,6 +49,10 @@ <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderAddresses" /> + <!-- MAGETWO-94169 --> + <data name="tag" xsi:type="string">stable:no</data> + <data name="issue" xsi:type="string">MAGETWO-94169: [MTF] - OnePageCheckoutUsingNonDefaultAddress_0 fails on 2.3-develop</data> + <!-- MAGETWO-94169 --> </variation> <variation name="OnePageCheckoutUsingNewAddress" summary="Checkout as Customer using New address" ticketId="MAGETWO-42601"> <data name="tag" xsi:type="string">severity:S1</data> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php index 37a4d5c26189f..42d6c4502ef49 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php @@ -123,6 +123,7 @@ public function run() } } $cart['data']['items'] = ['products' => $this->products]; + sleep(10); return ['cart' => $this->fixtureFactory->createByCode('cart', $cart)]; } } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php index 21da4c66fa2f3..0780b7d13a285 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php @@ -107,6 +107,7 @@ public function run() { $this->checkoutCart->open(); $this->checkoutCart->getCartBlock()->waitCartContainerLoading(); + sleep(20); /** @var \Magento\Checkout\Test\Fixture\Cart $cart */ if ($this->cart !== null) { $cart = $this->fixtureFactory->createByCode( diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php index 9df49d2440568..d951d84bab78d 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php @@ -91,8 +91,10 @@ public function __construct( */ public function run() { + sleep(20); $this->processLogin(); $this->processRegister(); + sleep(20); } /** diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php index 1c697d9f1e5da..f1a8b17ae2855 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php @@ -6,9 +6,6 @@ namespace Magento\Customer\Test\TestCase; -use Magento\Config\Test\Fixture\ConfigData; -use Magento\Customer\Test\Constraint\AssertChangingWebsiteChangeCountries; -use Magento\Framework\App\ObjectManager; use Magento\Mtf\Fixture\FixtureFactory; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\TestCase\Injectable; diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Catalog/Product/View/Links.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Catalog/Product/View/Links.php index 3d3920f28a733..648361b0a31c8 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Catalog/Product/View/Links.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Block/Catalog/Product/View/Links.php @@ -18,7 +18,7 @@ class Links extends Block { /** - * Selector title for for links + * Selector title for links * * @var string */ diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml index abfc9e09cb439..7d4811c4cb558 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Repository/Integration.xml @@ -40,6 +40,7 @@ <item name="9" xsi:type="string">Stores</item> <item name="10" xsi:type="string">System</item> <item name="11" xsi:type="string">Global Search</item> + <item name="12" xsi:type="string">Catalog</item> </field> </dataset> </repository> diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml index cae8dec6edcf1..11a8ed006af30 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/ActivateIntegrationEntityTest.xml @@ -14,7 +14,6 @@ <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationResourcesPopup" /> <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationTokensPopup" /> <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessActivationMessage" /> - <data name="issue" xsi:type="string">MAGETWO-66745: Magento\Integration\Test\TestCase\ActivateIntegrationEntityTest with ActivateIntegrationEntityTestVariation1 fails randomly</data> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Creditmemo/Form/Items.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Creditmemo/Form/Items.php index 74d522e9545a8..431116903d372 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Creditmemo/Form/Items.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Creditmemo/Form/Items.php @@ -51,6 +51,9 @@ public function getItemProductBlock($productSku) */ public function clickUpdateQty() { - $this->_rootElement->find($this->updateQty)->click(); + $button = $this->_rootElement->find($this->updateQty); + if (!$button->isDisabled()) { + $button->click(); + } } } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertNoCreditMemoButton.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertNoCreditMemoButton.php index 22ba984b489e2..c9bdbac4aa7ec 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertNoCreditMemoButton.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertNoCreditMemoButton.php @@ -30,7 +30,7 @@ public function processAssert(SalesOrderView $salesOrderView, OrderIndex $orderI $orderIndex->getSalesOrderGrid()->searchAndOpen(['id' => $order->getId()]); \PHPUnit\Framework\Assert::assertFalse( $salesOrderView->getPageActions()->isActionButtonVisible('Credit Memo'), - 'Credit memo button is present on order view page.' + 'Credit memo button should not be present on order view page.' ); } diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessfulReadinessCheck.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessfulReadinessCheck.php index 96fbc1905b5d0..676703924e59a 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessfulReadinessCheck.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/AssertSuccessfulReadinessCheck.php @@ -67,11 +67,13 @@ public function processAssert(SetupWizard $setupWizard) $setupWizard->getReadiness()->getDependencyCheck(), 'Dependency check is incorrect.' ); - \PHPUnit\Framework\Assert::assertContains( - self::PHP_VERSION_MESSAGE, - $setupWizard->getReadiness()->getPhpVersionCheck(), - 'PHP version is incorrect.' - ); + if ($setupWizard->getReadiness()->isPhpVersionCheckVisible()) { + \PHPUnit\Framework\Assert::assertContains( + self::PHP_VERSION_MESSAGE, + $setupWizard->getReadiness()->getPhpVersionCheck(), + 'PHP version is incorrect.' + ); + } \PHPUnit\Framework\Assert::assertContains( self::PHP_SETTING_REGEXP, $setupWizard->getReadiness()->getSettingsCheck(), diff --git a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/Extension/AssertVersionOnGrid.php b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/Extension/AssertVersionOnGrid.php index 068f748da4a8b..21ed8a24345f4 100644 --- a/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/Extension/AssertVersionOnGrid.php +++ b/dev/tests/functional/tests/app/Magento/Setup/Test/Constraint/Extension/AssertVersionOnGrid.php @@ -23,7 +23,7 @@ class AssertVersionOnGrid extends AbstractConstraint /*#@-*/ /** - * Assert that that version of extension is correct. + * Assert that version of extension is correct. * * @param Grid $grid * @param Extension $extension diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseInfo.php b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseInfo.php index 5fe6096035803..1529755fdc3c9 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseInfo.php +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseInfo.php @@ -73,7 +73,7 @@ class CaseInfo extends Block * * @var string */ - private $orderAmount = '[ng-bind*="currentCase.orderAmount"]'; + private $orderAmount = '[ng-bind*="currentCase.orderTotalAmount"]'; /** * Locator value for order amount currency. @@ -108,7 +108,7 @@ class CaseInfo extends Block * * @var string */ - private $shippingPrice = '[ng-if$="caseOrderSummary.shipments[0].shippingPrice"]'; + private $shippingPrice = '[ng-if="shipment.shippingPrice"]'; /** * Check if device data are present. diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseSearch.php b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseSearch.php index ef292de3a9e5f..16621f9a0cd48 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseSearch.php +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/CaseSearch.php @@ -33,6 +33,20 @@ class CaseSearch extends Form */ private $selectCaseLink = 'ul[case-list=cases] li[case-list-case=case] a'; + /** + * Locator for resolving applied filters list. + * + * @var string + */ + private $appliedFilters = '.app-taglist > ul > li > a'; + + /** + * Locator for loading spinner. + * + * @var string + */ + private $spinner = '.cases-loading-spinner'; + /** * Fill search input with customer name and submit. * @@ -41,8 +55,46 @@ class CaseSearch extends Form */ public function searchCaseByCustomerName($customerName) { + $this->resetFilters(); $this->_rootElement->find($this->searchBar)->setValue($customerName); $this->_rootElement->find($this->submitButton)->click(); + $this->waitLoadingSpinner(); + } + + /** + * Reset applied filters. + * + * @return void + */ + private function resetFilters(): void + { + $filters = $this->_rootElement->getElements($this->appliedFilters); + if (!empty($filters)) { + foreach ($filters as $filter) { + $filter->click(); + $this->waitLoadingSpinner(); + } + } + } + + /** + * Wait until loading spinner disappeared. + * + * @return void + */ + private function waitLoadingSpinner(): void + { + $this->waitForElementNotVisible($this->spinner); + } + + /** + * Checks if any case is visible. + * + * @return bool + */ + public function isAnyCaseVisible(): bool + { + return $this->_rootElement->find($this->selectCaseLink)->isVisible(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/SignifydLogin.php b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/SignifydLogin.php index 7f530afe4df63..7705a67360e55 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/SignifydLogin.php +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Block/SignifydConsole/SignifydLogin.php @@ -6,6 +6,8 @@ namespace Magento\Signifyd\Test\Block\SignifydConsole; use Magento\Mtf\Block\Form; +use Magento\Mtf\Client\Element\SimpleElement; +use Magento\Mtf\Fixture\FixtureInterface; /** * Signifyd login block. @@ -19,6 +21,23 @@ class SignifydLogin extends Form */ private $loginButton = '[type=submit]'; + /** + * Locator for admin form notification window. + * + * @var string + */ + private $notificationCloseButton = '.wm-close-button'; + + /** + * @inheritdoc + */ + public function fill(FixtureInterface $fixture, SimpleElement $element = null) + { + $this->closeNotification(); + + return parent::fill($fixture, $element); + } + /** * Login to Signifyd. * @@ -26,6 +45,20 @@ class SignifydLogin extends Form */ public function login() { + $this->closeNotification(); $this->_rootElement->find($this->loginButton)->click(); } + + /** + * Close notification popup. + * + * @return void + */ + private function closeNotification(): void + { + $notification = $this->browser->find($this->notificationCloseButton); + if ($notification->isVisible()) { + $notification->click(); + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestCase/CreateSignifydGuaranteeAndCancelOrderTest.xml b/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestCase/CreateSignifydGuaranteeAndCancelOrderTest.xml index 404229c2451cd..082627fe5821d 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestCase/CreateSignifydGuaranteeAndCancelOrderTest.xml +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestCase/CreateSignifydGuaranteeAndCancelOrderTest.xml @@ -62,7 +62,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertCancelInCommentsHistory" /> <constraint name="Magento\Signifyd\Test\Constraint\AssertSignifydCaseInCommentsHistory" /> <constraint name="Magento\Signifyd\Test\Constraint\AssertAwaitingSignifydGuaranteeInCommentsHistory" /> - <constraint name="Magento\Signifyd\Test\Constraint\AssertSignifydGuaranteeCancelInCommentsHistory" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestStep/SignifydObserveCaseStep.php b/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestStep/SignifydObserveCaseStep.php index 126e32489e6c9..c00c81fa237e0 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestStep/SignifydObserveCaseStep.php +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/TestStep/SignifydObserveCaseStep.php @@ -75,6 +75,11 @@ class SignifydObserveCaseStep implements TestStepInterface */ private $testStepFactory; + /** + * @var int + */ + private $searchAttempts = 10; + /** * @param AssertCaseInfoOnSignifydConsole $assertCaseInfoOnSignifydConsole * @param SignifydAddress $signifydAddress @@ -111,8 +116,16 @@ public function __construct( public function run() { $this->signifydCases->open(); - $this->signifydCases->getCaseSearchBlock() - ->searchCaseByCustomerName($this->signifydAddress->getFirstname()); + // Search case few times because it can appear with delay. + for ($attempts = $this->searchAttempts; $attempts > 0; $attempts--) { + $this->signifydCases->getCaseSearchBlock() + ->searchCaseByCustomerName($this->signifydAddress->getFirstname()); + if ($this->signifydCases->getCaseSearchBlock()->isAnyCaseVisible()) { + break; + } + sleep(3); + } + $this->signifydCases->getCaseSearchBlock()->selectCase(); $this->signifydCases->getCaseInfoBlock()->flagCase($this->signifydData->getCaseFlag()); diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php index c98b5a874a762..170fef137ec9f 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php @@ -6,7 +6,6 @@ namespace Magento\Ui\Test\Block\Adminhtml; -use Magento\Framework\Exception\LocalizedException; use Magento\Mtf\Fixture\InjectableFixture; /** @@ -53,13 +52,13 @@ protected function openContainer($sectionName) * * @param string $sectionName * @return $this - * @throws LocalizedException if section is not visible + * @throws \Exception if section is not visible */ public function openSection($sectionName) { $container = $this->getContainerElement($sectionName); if (!$container->isVisible()) { - throw new LocalizedException(__('Container is not found "' . $sectionName . '""')); + throw new \Exception('Container is not found "' . $sectionName . '""'); } $section = $container->find($this->collapsedSection); if ($section->isVisible()) { diff --git a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php index cc2763c0e3cb9..7f0c29da03784 100644 --- a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php +++ b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php @@ -44,7 +44,7 @@ public function __construct(FixtureFactory $fixtureFactory, array $params, $data $id = $this->entity->hasData('id') ? $this->entity->getId() : $this->entity->getPageId(); $this->data = preg_replace('`(%.*?%)`', $id, $data['entity']); } else { - $this->data = strval($data['entity']); + $this->data = (string)$data['entity']; } } diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php index abda2231c935f..0aa6d39497a43 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetCmsPageLink.php @@ -18,7 +18,7 @@ class AssertWidgetCmsPageLink extends AbstractConstraint { /** - * Assert that created widget displayed on frontent on Home page and on Advanced Search and + * Assert that created widget displayed on frontend on Home page and on Advanced Search and * after click on widget link on frontend system redirects you to cms page. * * @param CmsIndex $cmsIndex diff --git a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetRecentlyViewedProducts.php b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetRecentlyViewedProducts.php index 67eeb0cdc238b..e6c25fdc52ed5 100644 --- a/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetRecentlyViewedProducts.php +++ b/dev/tests/functional/tests/app/Magento/Widget/Test/Constraint/AssertWidgetRecentlyViewedProducts.php @@ -16,7 +16,7 @@ use Magento\Mtf\Constraint\AbstractConstraint; /** - * Check that that widget with type Recently Viewed Products is present on category page + * Check that widget with type Recently Viewed Products is present on category page */ class AssertWidgetRecentlyViewedProducts extends AbstractConstraint { diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php index c006516386274..803bee65fcdf4 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AbstractAssertWishlistProductDetails.php @@ -16,6 +16,11 @@ */ abstract class AbstractAssertWishlistProductDetails extends AbstractAssertForm { + /** + * @inheritdoc + */ + protected $skippedFields = ['sku']; + /** * Assert product details. * diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml index fd80d385e4853..95e6a854ed266 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/MoveProductFromShoppingCartToWishlistTest.xml @@ -37,7 +37,6 @@ <constraint name="Magento\Wishlist\Test\Constraint\AssertProductDetailsInWishlist" /> </variation> <variation name="MoveProductFromShoppingCartToWishlistTestVariation5"> - <data name="tag" xsi:type="string">stable:no</data> <data name="product/0" xsi:type="string">bundleProduct::bundle_dynamic_product</data> <constraint name="Magento\Wishlist\Test\Constraint\AssertMoveProductToWishlistSuccessMessage" /> <constraint name="Magento\Wishlist\Test\Constraint\AssertProductIsPresentInWishlist" /> @@ -45,7 +44,6 @@ <constraint name="Magento\Wishlist\Test\Constraint\AssertProductDetailsInWishlist" /> </variation> <variation name="MoveProductFromShoppingCartToWishlistTestVariation6"> - <data name="tag" xsi:type="string">stable:no</data> <data name="product/0" xsi:type="string">bundleProduct::bundle_fixed_product</data> <constraint name="Magento\Wishlist\Test\Constraint\AssertMoveProductToWishlistSuccessMessage" /> <constraint name="Magento\Wishlist\Test\Constraint\AssertProductIsPresentInWishlist" /> diff --git a/dev/tests/functional/utils/bootstrap.php b/dev/tests/functional/utils/bootstrap.php index 00c548e6b5cef..0b74dc6727a13 100644 --- a/dev/tests/functional/utils/bootstrap.php +++ b/dev/tests/functional/utils/bootstrap.php @@ -16,6 +16,3 @@ $objectManager = \Magento\Mtf\ObjectManagerFactory::getObjectManager(); \Magento\Mtf\ObjectManagerFactory::configure($objectManager); - -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); diff --git a/dev/tests/functional/utils/generate.php b/dev/tests/functional/utils/generate.php index e9a81a1eea224..61bcc3523f551 100644 --- a/dev/tests/functional/utils/generate.php +++ b/dev/tests/functional/utils/generate.php @@ -3,19 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem; require_once dirname(__FILE__) . '/' . 'bootstrap.php'; -// Generate fixtures -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); -// Remove previously generated static classes -$fs = $magentoObjectManager->create(Filesystem::class); -$fs->getDirectoryWrite(DirectoryList::ROOT)->delete('dev/tests/functional/generated/'); +deleteDirectory(MTF_BP . '/generated'); + +// Generate moduleSequence.json file +generateModuleSequence(); // Generate factories for old end-to-end tests -$magentoObjectManager->create(\Magento\Mtf\Util\Generate\Factory::class)->launch(); +$objectManager->create(\Magento\Mtf\Util\Generate\Factory::class)->launch(); $generatorPool = $objectManager->get('Magento\Mtf\Util\Generate\Pool'); foreach ($generatorPool->getGenerators() as $generator) { @@ -28,3 +24,28 @@ } \Magento\Mtf\Util\Generate\GenerateResult::displayResults(); + + +function deleteDirectory($dir) +{ + if (!file_exists($dir)) { + return true; + } + if (!is_dir($dir)) { + return unlink($dir); + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) { + return false; + } + } + return rmdir($dir); +} + +function generateModuleSequence() +{ + require_once "generate/moduleSequence.php"; +} diff --git a/dev/tests/functional/utils/generate/fixture.php b/dev/tests/functional/utils/generate/fixture.php index 9f454fb9dc5f1..68cdae4552261 100644 --- a/dev/tests/functional/utils/generate/fixture.php +++ b/dev/tests/functional/utils/generate/fixture.php @@ -5,6 +5,4 @@ */ require_once dirname(__DIR__) . '/' . 'bootstrap.php'; -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); $objectManager->create(\Magento\Mtf\Util\Generate\Fixture::class)->launch(); diff --git a/dev/tests/functional/utils/generate/moduleSequence.php b/dev/tests/functional/utils/generate/moduleSequence.php new file mode 100644 index 0000000000000..22688d1b75820 --- /dev/null +++ b/dev/tests/functional/utils/generate/moduleSequence.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +require_once __DIR__ . '/../../../../../app/bootstrap.php'; + +$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); +$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); +$magentoComponentSequence = $magentoObjectManager->create(\Magento\Framework\Module\ModuleList\Loader::class)->load(); +if (!file_exists(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated')) { + mkdir(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated'); +} +file_put_contents( + dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated' . DIRECTORY_SEPARATOR . 'moduleSequence.json', + json_encode($magentoComponentSequence, JSON_PRETTY_PRINT) +); diff --git a/dev/tests/functional/utils/generate/repository.php b/dev/tests/functional/utils/generate/repository.php index b2dc75c076f43..6633e776c9410 100644 --- a/dev/tests/functional/utils/generate/repository.php +++ b/dev/tests/functional/utils/generate/repository.php @@ -5,5 +5,4 @@ */ require_once dirname(__DIR__) . '/' . 'bootstrap.php'; -$magentoObjectManager->get(\Magento\Framework\App\State::class)->setAreaCode('frontend'); $objectManager->create(\Magento\Mtf\Util\Generate\Repository::class)->launch(); diff --git a/dev/tests/functional/utils/generateFixtureXml.php b/dev/tests/functional/utils/generateFixtureXml.php deleted file mode 100644 index 73fc266f34052..0000000000000 --- a/dev/tests/functional/utils/generateFixtureXml.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'bootstrap.php'; - -$magentoObjectManager->create( - \Magento\Mtf\Util\Generate\Fixture\SchemaXml::class, - ['objectManager' => $magentoObjectManager] -)->launch(); diff --git a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/Model/Config.php b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/Model/Config.php index 7726782c49f08..aceac618f48b9 100644 --- a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/Model/Config.php +++ b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/Model/Config.php @@ -19,17 +19,64 @@ class Config implements \Magento\Framework\Data\Wysiwyg\ConfigProviderInterface */ const CONFIG_CONTENT_CSS = 'something_else.css'; + /** @var \Magento\Cms\Model\Wysiwyg\DefaultConfigProvider */ + private $cmsConfigProvider; + + /** + * @param \Magento\Cms\Model\Wysiwyg\DefaultConfigProvider $cmsConfigProvider + */ + public function __construct(\Magento\Cms\Model\Wysiwyg\DefaultConfigProvider $cmsConfigProvider) + { + $this->cmsConfigProvider = $cmsConfigProvider; + } + /** * @inheritdoc */ public function getConfig(\Magento\Framework\DataObject $config): \Magento\Framework\DataObject { - $config->addData( + //get default config + $config = $this->cmsConfigProvider->getConfig($config); + + $config = $this->removeSpecialCharacterFromToolbar($config); + + $config = $this->modifyHeightAndContentCss($config); + return $config; + } + + /** + * Modify height and content_css in the config + * + * @param \Magento\Framework\DataObject $config + * @return \Magento\Framework\DataObject + */ + private function modifyHeightAndContentCss(\Magento\Framework\DataObject $config) : \Magento\Framework\DataObject + { + return $config->addData( [ 'height' => self::CONFIG_HEIGHT, 'content_css' => self::CONFIG_CONTENT_CSS ] ); + } + + /** + * Remove the special character from the toolbar configuration + * + * @param \Magento\Framework\DataObject $config + * @return \Magento\Framework\DataObject + */ + private function removeSpecialCharacterFromToolbar( + \Magento\Framework\DataObject $config + ) : \Magento\Framework\DataObject { + $tinymce4 = $config->getData('tinymce4'); + if (isset($tinymce4['toolbar']) && isset($tinymce4['plugins'])) { + $toolbar = $tinymce4['toolbar']; + $plugins = $tinymce4['plugins']; + $tinymce4['toolbar'] = str_replace('charmap', '', $toolbar); + $tinymce4['plugins'] = str_replace('charmap', '', $plugins); + $config->setData('tinymce4', $tinymce4); + } return $config; } } diff --git a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/adminhtml/di.xml b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/adminhtml/di.xml index 3366e5fc9685e..dd2e003f1a544 100644 --- a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/adminhtml/di.xml +++ b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/adminhtml/di.xml @@ -10,6 +10,7 @@ <arguments> <argument name="wysiwygConfigPostProcessor" xsi:type="array"> <item name="testAdapter" xsi:type="string">Magento\TestModuleWysiwygConfig\Model\Config</item> + <item name="Magento_TestModuleWysiwygConfig/wysiwyg/tinymce4TestAdapter" xsi:type="string">Magento\TestModuleWysiwygConfig\Model\Config</item> </argument> <argument name="variablePluginConfigProvider" xsi:type="array"> <item name="testAdapter" xsi:type="string">Magento\Variable\Model\Variable\ConfigProvider</item> @@ -22,4 +23,14 @@ </argument> </arguments> </type> + <type name="Magento\Cms\Model\Config\Source\Wysiwyg\Editor"> + <arguments> + <argument name="adapterOptions" xsi:type="array"> + <item name="testAdapter" xsi:type="array"> + <item name="value" xsi:type="string">Magento_TestModuleWysiwygConfig/wysiwyg/tinymce4TestAdapter</item> + <item name="label" xsi:type="string" translatable="true">Test Adapter</item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/di.xml b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/di.xml index cd9710db45b45..3be3d9dc3ec58 100644 --- a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/di.xml +++ b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/etc/di.xml @@ -9,7 +9,7 @@ <type name="Magento\Ui\Block\Wysiwyg\ActiveEditor"> <arguments> <argument name="availableAdapterPaths" xsi:type="array"> - <item name="testAdapter" xsi:type="string"/> + <item name="Magento_TestModuleWysiwygConfig/wysiwyg/tinymce4TestAdapter" xsi:type="string"/> </argument> </arguments> </type> diff --git a/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/view/adminhtml/web/wysiwyg/tinymce4TestAdapter.js b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/view/adminhtml/web/wysiwyg/tinymce4TestAdapter.js new file mode 100644 index 0000000000000..005e6437c2d78 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleWysiwygConfig/view/adminhtml/web/wysiwyg/tinymce4TestAdapter.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* global popups, tinyMceEditors, MediabrowserUtility, Base64 */ +/* eslint-disable strict */ +define([ + 'mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter' +], function (tinyMCE4) { + 'use strict'; + + return tinyMCE4; +}); diff --git a/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php new file mode 100644 index 0000000000000..9f8518239cce5 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Annotation; + +use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher; +use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcherConfiguration; +use Magento\Catalog\Model\Indexer\Product\Price\Processor; +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\App\Config; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; +use PHPUnit\Framework\TestCase; + +/** + * Implementation of the @magentoIndexerDimensionMode DocBlock annotation + */ +class IndexerDimensionMode +{ + /** @var TypeListInterface */ + private $cacheTypeList; + + /** @var ScopeConfigInterface */ + private $configReader; + + /** @var ModeSwitcher */ + private $modeSwitcher; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var \Magento\TestFramework\Db\Mysql */ + private $db; + + /** @var bool */ + private $isDimensionMode = false; + + /** + * Restore db + */ + private function restoreDb() + { + $this->db = Bootstrap::getInstance()->getBootstrap()->getApplication()->getDbInstance(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->db->restoreFromDbDump(); + $this->cacheTypeList = $this->objectManager->get(TypeListInterface::class); + $this->cacheTypeList->cleanType('config'); + $this->objectManager->get(Config::class)->clean(); + } + + /** + * @param string $mode + * @param TestCase $test + * @throws \Exception + */ + private function setDimensionMode(string $mode, TestCase $test) + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->modeSwitcher = $this->objectManager->get(ModeSwitcher::class); + $this->configReader = $this->objectManager->get(ScopeConfigInterface::class); + $this->cacheTypeList = $this->objectManager->get(TypeListInterface::class); + + $this->configReader->clean(); + $previousMode = $this->configReader->getValue(ModeSwitcherConfiguration::XML_PATH_PRICE_DIMENSIONS_MODE) ?: + DimensionModeConfiguration::DIMENSION_NONE; + + if ($previousMode !== $mode) { + //Create new tables and move data + $this->modeSwitcher->switchMode($mode, $previousMode); + $this->objectManager->get(Config::class)->clean(); + } else { + $this->fail('Dimensions mode for indexer has not been changed', $test); + } + } + + /** + * Handler for 'startTest' event + * + * @param TestCase $test + * @return void + * @throws \Exception + */ + public function startTest(TestCase $test) + { + $source = $test->getAnnotations(); + + if (isset($source['method']['magentoIndexerDimensionMode'])) { + $annotations = $source['method']['magentoIndexerDimensionMode']; + } elseif (isset($source['class']['magentoIndexerDimensionMode'])) { + $annotations = $source['class']['magentoIndexerDimensionMode']; + } else { + return; + } + + $dbIsolation = $source['method']['magentoDbIsolation'] + ?? $source['class']['magentoDbIsolation'] + ?? ['disabled']; + + if ($dbIsolation[0] != 'disabled') { + $this->fail("Invalid @magentoDbIsolation declaration: $dbIsolation[0]", $test); + } + + list($indexerType, $indexerMode) = explode(' ', $annotations[0]); + + if ($indexerType == Processor::INDEXER_ID) { + $this->isDimensionMode = true; + $this->setDimensionMode($indexerMode, $test); + } + } + + /** + * Handler for 'endTest' event + * + * @return void + */ + public function endTest() + { + if ($this->isDimensionMode) { + $this->restoreDb(); + $this->isDimensionMode = false; + } + } + + /** + * Fails the test with specified error message + * + * @param string $message + * @param TestCase $test + * @throws \Exception + */ + private function fail($message, TestCase $test) + { + $test->fail("{$message} in the test '{$test->toString()}'"); + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/Bootstrap/DocBlock.php b/dev/tests/integration/framework/Magento/TestFramework/Bootstrap/DocBlock.php index d0b6d0427bbd3..9a6b808ef7d61 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Bootstrap/DocBlock.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Bootstrap/DocBlock.php @@ -5,6 +5,8 @@ */ namespace Magento\TestFramework\Bootstrap; +use Magento\TestFramework\Application; + /** * Bootstrap of the custom DocBlock annotations * @@ -27,8 +29,9 @@ public function __construct($fixturesBaseDir) /** * Activate custom DocBlock annotations along with more-or-less permanent workarounds + * @param Application $application */ - public function registerAnnotations(\Magento\TestFramework\Application $application) + public function registerAnnotations(Application $application) { $eventManager = new \Magento\TestFramework\EventManager($this->_getSubscribers($application)); \Magento\TestFramework\Event\PhpUnit::setDefaultEventManager($eventManager); @@ -42,10 +45,10 @@ public function registerAnnotations(\Magento\TestFramework\Application $applicat * To allow config fixtures to deal with fixture stores, data fixtures should be processed first. * ConfigFixture applied twice because data fixtures could clean config and clean custom settings * - * @param \Magento\TestFramework\Application $application + * @param Application $application * @return array */ - protected function _getSubscribers(\Magento\TestFramework\Application $application) + protected function _getSubscribers(Application $application) { return [ new \Magento\TestFramework\Workaround\Segfault(), @@ -54,6 +57,7 @@ protected function _getSubscribers(\Magento\TestFramework\Application $applicati new \Magento\TestFramework\Isolation\WorkingDirectory(), new \Magento\TestFramework\Isolation\DeploymentConfig(), new \Magento\TestFramework\Annotation\AppIsolation($application), + new \Magento\TestFramework\Annotation\IndexerDimensionMode($application), new \Magento\TestFramework\Isolation\AppConfig(), new \Magento\TestFramework\Annotation\ConfigFixture(), new \Magento\TestFramework\Annotation\DataFixtureBeforeTransaction($this->_fixturesBaseDir), diff --git a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php index 9920f90193f69..1bf6d471fc40a 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractController.php @@ -9,9 +9,11 @@ */ namespace Magento\TestFramework\TestCase; +use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\Framework\View\Element\Message\InterpretationStrategyInterface; use Magento\Theme\Controller\Result\MessagePlugin; +use Magento\Framework\App\Request\Http as HttpRequest; /** * @SuppressWarnings(PHPMD.NumberOfChildren) @@ -96,7 +98,16 @@ protected function assertPostConditions() */ public function dispatch($uri) { - $this->getRequest()->setRequestUri($uri); + /** @var HttpRequest $request */ + $request = $this->getRequest(); + $request->setRequestUri($uri); + if ($request->isPost() + && !array_key_exists('form_key', $request->getPost()) + ) { + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $request->setPostValue('form_key', $formKey->getFormKey()); + } $this->_getBootstrap()->runApp(); } diff --git a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricingTest.php b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricingTest.php index 1aea568395c99..481cc629c6777 100644 --- a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricingTest.php +++ b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Export/AdvancedPricingTest.php @@ -7,7 +7,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; -class AdvancedPricingTest extends \PHPUnit\Framework\TestCase +class AdvancedPricingTest extends \Magento\TestFramework\Indexer\TestCase { /** * @var \Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing @@ -24,6 +24,19 @@ class AdvancedPricingTest extends \PHPUnit\Framework\TestCase */ protected $fileSystem; + public static function setUpBeforeClass() + { + $db = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + protected function setUp() { parent::setUp(); @@ -37,7 +50,7 @@ protected function setUp() /** * @magentoAppArea adminhtml - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @magentoDataFixture Magento/Catalog/_files/product_simple.php */ @@ -49,6 +62,7 @@ public function testExport() $index = 0; $ids = []; $origPricingData = []; + $skus = ['simple']; while (isset($skus[$index])) { $ids[$index] = $productRepository->get($skus[$index])->getId(); $origPricingData[$index] = $this->objectManager->create(\Magento\Catalog\Model\Product::class) @@ -94,7 +108,7 @@ private function assertDiscountTypes($exportContent) /** * @magentoAppArea adminhtml - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @magentoConfigFixture current_store catalog/price/scope 1 * @magentoDataFixture Magento/AdvancedPricingImportExport/_files/product_with_second_website.php diff --git a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/_files/create_products.php b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/_files/create_products.php index f625ee3288d5d..ef5877612a3b9 100644 --- a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/_files/create_products.php +++ b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/_files/create_products.php @@ -15,7 +15,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['qty' => 100, 'is_in_stock' => 1, 'manage_stock' => 1]) ->setIsObjectNew(true) ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php b/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php new file mode 100644 index 0000000000000..21ffddf851ac4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php @@ -0,0 +1,433 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Backend\App\Request; + +use Magento\Backend\App\AbstractAction; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\Auth; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Phrase; +use Magento\TestFramework\Request; +use Magento\TestFramework\Response; +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Bootstrap as TestBootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\App\Response\Http as HttpResponse; +use Zend\Stdlib\Parameters; +use Magento\Backend\Model\UrlInterface as BackendUrl; +use Magento\Framework\App\Response\HttpFactory as HttpResponseFactory; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class BackendValidatorTest extends TestCase +{ + private const AWARE_VALIDATION_PARAM = 'test_param'; + + private const AWARE_LOCATION_VALUE = 'test1'; + + private const CSRF_AWARE_MESSAGE = 'csrf_aware'; + + /** + * @var ActionInterface + */ + private $mockUnawareAction; + + /** + * @var AbstractAction + */ + private $mockAwareAction; + + /** + * @var BackendValidator + */ + private $validator; + + /** + * @var Request + */ + private $request; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @var BackendUrl + */ + private $url; + + /** + * @var Auth + */ + private $auth; + + /** + * @var CsrfAwareActionInterface + */ + private $mockCsrfAwareAction; + + /** + * @var HttpResponseFactory + */ + private $httpResponseFactory; + + /** + * @return ActionInterface + */ + private function createUnawareAction(): ActionInterface + { + return new class implements ActionInterface { + /** + * @inheritDoc + */ + public function execute() + { + throw new NotFoundException(new Phrase('Not implemented')); + } + }; + } + + /** + * @return AbstractAction + */ + private function createAwareAction(): AbstractAction + { + $l = self::AWARE_LOCATION_VALUE; + $p = self::AWARE_VALIDATION_PARAM; + + return new class($l, $p) extends AbstractAction{ + + /** + * @var string + */ + private $locationValue; + + /** + * @var string + */ + private $param; + + /** + * @param string $locationValue + * @param string $param + */ + public function __construct( + string $locationValue, + string $param + ) { + parent::__construct( + Bootstrap::getObjectManager()->get(Context::class) + ); + $this->locationValue= $locationValue; + $this->param = $param; + } + + /** + * @inheritDoc + */ + public function execute() + { + throw new NotFoundException(new Phrase('Not implemented')); + } + + /** + * @inheritDoc + */ + public function _processUrlKeys() + { + if ($this->_request->getParam($this->param)) { + return true; + } else { + /** @var Response $response */ + $response = $this->_response; + $response->setHeader('Location', $this->locationValue); + + return false; + } + } + }; + } + + /** + * @return CsrfAwareActionInterface + */ + private function createCsrfAwareAction(): CsrfAwareActionInterface + { + $r = Bootstrap::getObjectManager() + ->get(ResponseInterface::class); + $m = self::CSRF_AWARE_MESSAGE; + + return new class ($r, $m) implements CsrfAwareActionInterface { + + /** + * @var ResponseInterface + */ + private $response; + + /** + * @var string + */ + private $message; + + /** + * @param ResponseInterface $response + * @param string $message + */ + public function __construct( + ResponseInterface $response, + string $message + ) { + $this->response = $response; + $this->message = $message; + } + + /** + * @inheritDoc + */ + public function execute() + { + return $this->response; + } + + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return new InvalidRequestException( + $this->response, + [new Phrase($this->message)] + ); + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return false; + } + + }; + } + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->request = $objectManager->get(RequestInterface::class); + $this->validator = $objectManager->get(BackendValidator::class); + $this->mockUnawareAction = $this->createUnawareAction(); + $this->mockAwareAction = $this->createAwareAction(); + $this->formKey = $objectManager->get(FormKey::class); + $this->url = $objectManager->get(BackendUrl::class); + $this->auth = $objectManager->get(Auth::class); + $this->mockCsrfAwareAction = $this->createCsrfAwareAction(); + $this->httpResponseFactory = $objectManager->get( + HttpResponseFactory::class + ); + } + + /** + * @magentoConfigFixture admin/security/use_form_key 1 + * @magentoAppArea adminhtml + */ + public function testValidateWithValidKey() + { + $this->request->setMethod(HttpRequest::METHOD_GET); + $this->auth->login( + TestBootstrap::ADMIN_NAME, + TestBootstrap::ADMIN_PASSWORD + ); + $this->request->setParams([ + BackendUrl::SECRET_KEY_PARAM_NAME => $this->url->getSecretKey(), + ]); + + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @expectedException \Magento\Framework\App\Request\InvalidRequestException + * + * @magentoConfigFixture admin/security/use_form_key 1 + * @magentoAppArea adminhtml + */ + public function testValidateWithInvalidKey() + { + $invalidKey = $this->url->getSecretKey() .'Invalid'; + $this->request->setParams([ + BackendUrl::SECRET_KEY_PARAM_NAME => $invalidKey, + ]); + $this->request->setMethod(HttpRequest::METHOD_GET); + $this->auth->login( + TestBootstrap::ADMIN_NAME, + TestBootstrap::ADMIN_PASSWORD + ); + + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @expectedException \Magento\Framework\App\Request\InvalidRequestException + * + * @magentoConfigFixture admin/security/use_form_key 0 + * @magentoAppArea adminhtml + */ + public function testValidateWithInvalidFormKey() + { + $this->request->setPost( + new Parameters(['form_key' => $this->formKey->getFormKey() .'1']) + ); + $this->request->setMethod(HttpRequest::METHOD_POST); + + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @magentoConfigFixture admin/security/use_form_key 0 + * @magentoAppArea adminhtml + */ + public function testValidateInvalidWithAwareAction() + { + $this->request->setParams([self::AWARE_VALIDATION_PARAM => '']); + + /** @var InvalidRequestException|null $caught */ + $caught = null; + try { + $this->validator->validate( + $this->request, + $this->mockAwareAction + ); + } catch (InvalidRequestException $exception) { + $caught = $exception; + } + + $this->assertNotNull($caught); + /** @var Response $response */ + $response = $caught->getReplaceResult(); + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals( + self::AWARE_LOCATION_VALUE, + $response->getHeader('Location')->getFieldValue() + ); + $this->assertNull($caught->getMessages()); + } + + /** + * @magentoAppArea adminhtml + */ + public function testValidateValidWithAwareAction() + { + $this->request->setParams( + [self::AWARE_VALIDATION_PARAM => '1'] + ); + + $this->validator->validate( + $this->request, + $this->mockAwareAction + ); + } + + /** + * @magentoConfigFixture admin/security/use_form_key 1 + * @magentoAppArea adminhtml + */ + public function testValidateWithCsrfAwareAction() + { + //Setting up request that would be valid for default validation. + $this->request->setMethod(HttpRequest::METHOD_GET); + $this->auth->login( + TestBootstrap::ADMIN_NAME, + TestBootstrap::ADMIN_PASSWORD + ); + $this->request->setParams([ + BackendUrl::SECRET_KEY_PARAM_NAME => $this->url->getSecretKey(), + ]); + + /** @var InvalidRequestException|null $caught */ + $caught = null; + try { + $this->validator->validate( + $this->request, + $this->mockCsrfAwareAction + ); + } catch (InvalidRequestException $exception) { + $caught = $exception; + } + + //Checking that custom validation was called and invalidated + //valid request. + $this->assertNotNull($caught); + $this->assertCount(1, $caught->getMessages()); + $this->assertEquals( + self::CSRF_AWARE_MESSAGE, + $caught->getMessages()[0]->getText() + ); + } + + public function testInvalidAjaxRequest() + { + //Setting up AJAX request with invalid secret key. + $this->request->setMethod(HttpRequest::METHOD_GET); + $this->auth->login( + TestBootstrap::ADMIN_NAME, + TestBootstrap::ADMIN_PASSWORD + ); + $this->request->setParams([ + BackendUrl::SECRET_KEY_PARAM_NAME => 'invalid', + 'isAjax' => '1' + ]); + + /** @var InvalidRequestException|null $caught */ + $caught = null; + try { + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } catch (InvalidRequestException $exception) { + $caught = $exception; + } + + $this->assertNotNull($caught); + $this->assertInstanceOf( + ResultInterface::class, + $caught->getReplaceResult() + ); + /** @var ResultInterface $result */ + $result = $caught->getReplaceResult(); + /** @var HttpResponse $response */ + $response = $this->httpResponseFactory->create(); + $result->renderResult($response); + $this->assertEmpty($response->getBody()); + $this->assertEquals(401, $response->getHttpResponseCode()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/CacheTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/CacheTest.php index 04930661efb43..6836161fd6ec2 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/CacheTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/CacheTest.php @@ -73,7 +73,7 @@ public function testMassActionsInvalidTypes($action) $this->getRequest()->setParams(['types' => ['invalid_type_1', 'invalid_type_2', 'config']]); $this->dispatch('backend/admin/cache/' . $action); $this->assertSessionMessages( - $this->contains("These cache type(s) don't exist: invalid_type_1, invalid_type_2"), + $this->contains("These cache type(s) don't exist: invalid_type_1, invalid_type_2"), \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Adminhtml/Invoice/CreateTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Adminhtml/Invoice/CreateTest.php new file mode 100644 index 0000000000000..d6ea08a2f7ca3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Adminhtml/Invoice/CreateTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Controller\Adminhtml\Invoice; + +use Braintree\Result\Successful; +use Braintree\Transaction; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @magentoAppArea adminhtml + */ +class CreateTest extends AbstractBackendController +{ + /** + * @var BraintreeAdapter|MockObject + */ + private $adapter; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $adapterFactory = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactory->method('create') + ->willReturn($this->adapter); + + $this->_objectManager->addSharedInstance($adapterFactory, BraintreeAdapterFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->_objectManager->removeSharedInstance(BraintreeAdapterFactory::class); + parent::tearDown(); + } + + /** + * Checks a case when non default Merchant Account ID should be send to Braintree + * during creation second partial invoice. + * + * @return void + * @magentoConfigFixture default_store payment/braintree/merchant_account_id Magneto + * @magentoConfigFixture current_store payment/braintree/merchant_account_id USA_Merchant + * @magentoDataFixture Magento/Braintree/Fixtures/partial_invoice.php + */ + public function testCreatePartialInvoiceWithNonDefaultMerchantAccount(): void + { + $order = $this->getOrder('100000002'); + + $this->adapter->method('sale') + ->with(self::callback(function ($request) { + self::assertEquals('USA_Merchant', $request['merchantAccountId']); + return true; + })) + ->willReturn($this->getTransactionStub()); + + $uri = 'backend/sales/order_invoice/save/order_id/' . $order->getEntityId(); + $this->prepareRequest($uri); + $this->dispatch($uri); + + self::assertSessionMessages( + self::equalTo(['The invoice has been created.']), + MessageInterface::TYPE_SUCCESS + ); + } + + /** + * Creates stub for Braintree capture Transaction. + * + * @return Successful + */ + private function getTransactionStub(): Successful + { + $transaction = $this->getMockBuilder(Transaction::class) + ->disableOriginalConstructor() + ->getMock(); + $transaction->status = 'submitted_for_settlement'; + $response = new Successful(); + $response->success = true; + $response->transaction = $transaction; + + return $response; + } + + /** + * Gets order by increment ID. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrder(string $incrementId): OrderInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', $incrementId) + ->create(); + + /** @var OrderRepositoryInterface $repository */ + $repository = $this->_objectManager->get(OrderRepositoryInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } + + /** + * Prepares POST request for invoice creation. + * + * @param string $uri + * @return void + */ + private function prepareRequest(string $uri): void + { + /** @var FormKey $formKey */ + $formKey = $this->_objectManager->get(FormKey::class); + $request = $this->getRequest(); + $request->setMethod('POST'); + $request->setParam('form_key', $formKey->getFormKey()); + $request->setRequestUri($uri); + $request->setPostValue( + [ + 'invoice' => [ + 'capture_case' => 'online' + ] + ] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order.php new file mode 100644 index 0000000000000..ceb90710ed5e7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$addressData = include __DIR__ . '/../../Sales/_files/address_data.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null) + ->setAddressType('shipping'); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); + +require __DIR__ . '/payment.php'; + +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000002') + ->setSubtotal($product->getPrice() * 2) + ->setBaseSubtotal($product->getPrice() * 2) + ->setCustomerEmail('admin@example.com') + ->setCustomerIsGuest(true) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId( + $objectManager->get(StoreManagerInterface::class)->getStore() + ->getId() + ) + ->addItem($orderItem) + ->setPayment($payment); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order_rollback.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order_rollback.php new file mode 100644 index 0000000000000..a2da0b639e98d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/order_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\TestFramework\ObjectManager; + +$objectManager = ObjectManager::getInstance(); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', '100000002') + ->create(); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$items = $orderRepository->getList($searchCriteria) + ->getItems(); + +foreach ($items as $item) { + $orderRepository->delete($item); +} + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice.php new file mode 100644 index 0000000000000..22b954515f3b5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Service\InvoiceService; +use Magento\TestFramework\ObjectManager; + +/** @var Order $order */ + +require __DIR__ . '/order.php'; + +$objectManager = ObjectManager::getInstance(); + +/** @var InvoiceService $invoiceService */ +$invoiceService = $objectManager->get(InvoiceService::class); +$invoice = $invoiceService->prepareInvoice($order); +$invoice->setIncrementId('100000002'); +$invoice->register(); + +$items = $invoice->getAllItems(); +$item = array_pop($items); +$item->setQty(1); +$invoice->setTotalQty(1); + +$items = $order->getAllItems(); +/** @var \Magento\Sales\Api\Data\OrderItemInterface $item */ +$item = array_pop($items); +$item->setQtyInvoiced(1); +$invoice->collectTotals(); + +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +$invoice = $invoiceRepository->save($invoice); + +/** @var TransactionRepositoryInterface $transactionRepository */ +$transactionRepository = $objectManager->get(TransactionRepositoryInterface::class); +$transaction = $transactionRepository->create(); +$transaction->setTxnType('capture'); +$transaction->setPaymentId($order->getPayment()->getEntityId()); +$transaction->setOrderId($order->getEntityId()); +$transactionRepository->save($transaction); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice_rollback.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice_rollback.php new file mode 100644 index 0000000000000..1ed4438f87db2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/partial_invoice_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\TestFramework\ObjectManager; + +$objectManager = ObjectManager::getInstance(); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', '%10000000%', 'like') + ->create(); + +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +$items = $invoiceRepository->getList($searchCriteria) + ->getItems(); + +foreach ($items as $item) { + $invoiceRepository->delete($item); +} + +require __DIR__ . '/order_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment.php new file mode 100644 index 0000000000000..a4285b963bffa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Sales\Model\Order\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +require __DIR__ . '/../../Vault/_files/token.php'; + +$token->setPaymentMethodCode(ConfigProvider::CODE); +/** @var OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory */ +$paymentExtensionFactory = $objectManager->get(OrderPaymentExtensionInterfaceFactory::class); +$extensionAttributes = $paymentExtensionFactory->create(); +$extensionAttributes->setVaultPaymentToken($token); + +/** @var Payment $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(ConfigProvider::CODE); +$payment->setExtensionAttributes($extensionAttributes); +$payment->setAuthorizationTransaction(true); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Block/Catalog/Product/View/Type/BundleTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Block/Catalog/Product/View/Type/BundleTest.php index 35c470282937f..ce324ed774dc4 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Block/Catalog/Product/View/Type/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Block/Catalog/Product/View/Type/BundleTest.php @@ -11,6 +11,7 @@ * Test for Magento\Bundle\Block\Catalog\Product\View\Type\Bundle * * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation disabled * @magentoAppArea frontend */ class BundleTest extends \PHPUnit\Framework\TestCase @@ -73,6 +74,9 @@ public function testGetJsonConfig() $this->assertEquals(5, $selection['prices']['finalPrice']['amount']); } + /** + * Tear Down + */ protected function tearDown() { $this->objectManager->get(\Magento\Framework\Registry::class)->unregister('product'); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Controller/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Controller/ProductTest.php index 1f2f08f345099..65a9b1234ef7c 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Controller/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Controller/ProductTest.php @@ -13,6 +13,7 @@ class ProductTest extends \Magento\TestFramework\TestCase\AbstractController { /** * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation disabled */ public function testViewAction() { diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/DynamicBundlePriceCalculatorWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/DynamicBundlePriceCalculatorWithDimensionTest.php new file mode 100644 index 0000000000000..2123968d64892 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/DynamicBundlePriceCalculatorWithDimensionTest.php @@ -0,0 +1,350 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Product; + +/** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @group indexer_dimension + * @magentoAppArea frontend + */ +class DynamicBundlePriceCalculatorWithDimensionTest extends BundlePriceAbstract +{ + /** + * @param array $strategyModifiers + * @param array $expectedResults + * @dataProvider getTestCases + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Bundle/_files/PriceCalculator/dynamic_bundle_product.php + * @magentoDbIsolation disabled + */ + public function testPriceForDynamicBundle(array $strategyModifiers, array $expectedResults) + { + $this->prepareFixture($strategyModifiers, 'bundle_product'); + $bundleProduct = $this->productRepository->get('bundle_product', false, null, true); + + /** @var \Magento\Framework\Pricing\PriceInfo\Base $priceInfo */ + $priceInfo = $bundleProduct->getPriceInfo(); + $priceCode = \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE; + + $this->assertEquals( + $expectedResults['minimalPrice'], + $priceInfo->getPrice($priceCode)->getMinimalPrice()->getValue(), + 'Failed to check minimal price on product' + ); + $this->assertEquals( + $expectedResults['maximalPrice'], + $priceInfo->getPrice($priceCode)->getMaximalPrice()->getValue(), + 'Failed to check maximal price on product' + ); + + $priceInfoFromIndexer = $this->productCollectionFactory->create() + ->addFieldToFilter('sku', 'bundle_product') + ->addPriceData() + ->load() + ->getFirstItem(); + + $this->assertEquals($expectedResults['minimalPrice'], $priceInfoFromIndexer->getMinimalPrice()); + $this->assertEquals($expectedResults['maximalPrice'], $priceInfoFromIndexer->getMaxPrice()); + } + + /** + * @param array $strategyModifiers + * @param array $expectedResults + * @dataProvider getTestCases + * @magentoAppIsolation enabled + * @magentoConfigFixture current_store catalog/price/scope 1 + * @magentoDataFixture Magento/Bundle/_files/PriceCalculator/dynamic_bundle_product.php + * @magentoDbIsolation disabled + */ + public function testPriceForDynamicBundleInWebsiteScope(array $strategyModifiers, array $expectedResults) + { + $this->prepareFixture($strategyModifiers, 'bundle_product'); + $bundleProduct = $this->productRepository->get('bundle_product', false, null, true); + + /** @var \Magento\Framework\Pricing\PriceInfo\Base $priceInfo */ + $priceInfo = $bundleProduct->getPriceInfo(); + $priceCode = \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE; + + $this->assertEquals( + $expectedResults['minimalPrice'], + $priceInfo->getPrice($priceCode)->getMinimalPrice()->getValue(), + 'Failed to check minimal price on product' + ); + $this->assertEquals( + $expectedResults['maximalPrice'], + $priceInfo->getPrice($priceCode)->getMaximalPrice()->getValue(), + 'Failed to check maximal price on product' + ); + + $priceInfoFromIndexer = $this->productCollectionFactory->create() + ->addFieldToFilter('sku', 'bundle_product') + ->addPriceData() + ->load() + ->getFirstItem(); + + $this->assertEquals($expectedResults['minimalPrice'], $priceInfoFromIndexer->getMinimalPrice()); + $this->assertEquals($expectedResults['maximalPrice'], $priceInfoFromIndexer->getMaxPrice()); + } + + /** + * Test cases for current test + * @return array + */ + public function getTestCases() + { + return [ + '#1 Testing price for dynamic bundle product with one simple' => [ + 'strategy' => $this->getBundleConfiguration1(), + 'expectedResults' => [ + // just price from simple1 + 'minimalPrice' => 10, + // just price from simple1 + 'maximalPrice' => 10 + ] + ], + + '#2 Testing price for dynamic bundle product with three simples and different qty' => [ + 'strategy' => $this->getBundleConfiguration2(), + 'expectedResults' => [ + // min price from simples 3*10 or 30 + 'minimalPrice' => 30, + // (3 * 10) + (2 * 20) + 30 + 'maximalPrice' => 100 + ] + ], + + '#3 Testing price for dynamic bundle product with four simples and different price' => [ + 'strategy' => $this->getBundleConfiguration3(), + 'expectedResults' => [ + // 10 + 'minimalPrice' => 10, + // 10 + 20 + 30 + 'maximalPrice' => 60 + ] + ], + + '#4 Testing price for dynamic bundle with two non required options' => [ + 'strategy' => $this->getBundleConfiguration4(), + 'expectedResults' => [ + // 1 * 10 + 'minimalPrice' => 10, + // 3 * 20 + 1 * 10 + 3 * 20 + 'maximalPrice' => 130 + ] + ], + + '#5 Testing price for dynamic bundle with two required options' => [ + 'strategy' => $this->getBundleConfiguration5(), + 'expectedResults' => [ + // 1 * 10 + 1 * 10 + 'minimalPrice' => 20, + // 3 * 20 + 1 * 10 + 3 * 20 + 'maximalPrice' => 130 + ] + ], + ]; + } + + /** + * Dynamic bundle product with one simple + * + * @return array + */ + private function getBundleConfiguration1() + { + $optionsData = [ + [ + 'title' => 'op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + ] + ], + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Dynamic bundle product with three simples and different qty + * + * @return array + */ + private function getBundleConfiguration2() + { + $optionsData = [ + [ + 'title' => 'op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 3, + ], + [ + 'sku' => 'simple2', + 'qty' => 2, + ], + [ + 'sku' => 'simple3', + 'qty' => 1, + ], + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Dynamic bundle product with three simples and different price + * + * @return array + */ + private function getBundleConfiguration3() + { + $optionsData = [ + [ + 'title' => 'op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + [ + 'sku' => 'simple2', + 'qty' => 1, + ], + [ + 'sku' => 'simple3', + 'qty' => 1, + ] + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Dynamic bundle with two non required options and special price + * @return array + */ + private function getBundleConfiguration4() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => false, + 'type' => 'radio', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + [ + 'sku' => 'simple2', + 'qty' => 3, + ], + ] + ], + [ + 'title' => 'Op2', + 'required' => false, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + [ + 'sku' => 'simple2', + 'qty' => 3, + ], + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Dynamic bundle with two required options + * @return array + */ + private function getBundleConfiguration5() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'radio', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + [ + 'sku' => 'simple2', + 'qty' => 3, + ], + ] + ], + [ + 'title' => 'Op2', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + ], + [ + 'sku' => 'simple2', + 'qty' => 3, + ], + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php new file mode 100644 index 0000000000000..b97bd9f822666 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php @@ -0,0 +1,417 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Product; + +use \Magento\Bundle\Api\Data\LinkInterface; + +/** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @group indexer_dimension + * @magentoAppArea frontend + */ +class FixedBundlePriceCalculatorWithDimensionTest extends BundlePriceAbstract +{ + /** + * @param array $strategyModifiers + * @param array $expectedResults + * @dataProvider getTestCases + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Bundle/_files/PriceCalculator/fixed_bundle_product.php + * @magentoDbIsolation disabled + */ + public function testPriceForFixedBundle(array $strategyModifiers, array $expectedResults) + { + $this->prepareFixture($strategyModifiers, 'bundle_product'); + $bundleProduct = $this->productRepository->get('bundle_product', false, null, true); + + /** @var \Magento\Framework\Pricing\PriceInfo\Base $priceInfo */ + $priceInfo = $bundleProduct->getPriceInfo(); + $priceCode = \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE; + + $this->assertEquals( + $expectedResults['minimalPrice'], + $priceInfo->getPrice($priceCode)->getMinimalPrice()->getValue(), + 'Failed to check minimal price on product' + ); + $this->assertEquals( + $expectedResults['maximalPrice'], + $priceInfo->getPrice($priceCode)->getMaximalPrice()->getValue(), + 'Failed to check maximal price on product' + ); + + $priceInfoFromIndexer = $this->productCollectionFactory->create() + ->addIdFilter([42]) + ->addPriceData() + ->load() + ->getFirstItem(); + + $this->assertEquals($expectedResults['minimalPrice'], $priceInfoFromIndexer->getMinimalPrice()); + $this->assertEquals($expectedResults['maximalPrice'], $priceInfoFromIndexer->getMaxPrice()); + } + + /** + * @param array $strategyModifiers + * @param array $expectedResults + * @dataProvider getTestCases + * @magentoAppIsolation enabled + * @magentoConfigFixture current_store catalog/price/scope 1 + * @magentoDataFixture Magento/Bundle/_files/PriceCalculator/fixed_bundle_product.php + * @magentoDbIsolation disabled + */ + public function testPriceForFixedBundleInWebsiteScope(array $strategyModifiers, array $expectedResults) + { + $this->prepareFixture($strategyModifiers, 'bundle_product'); + $bundleProduct = $this->productRepository->get('bundle_product', false, null, true); + + /** @var \Magento\Framework\Pricing\PriceInfo\Base $priceInfo */ + $priceInfo = $bundleProduct->getPriceInfo(); + $priceCode = \Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE; + + $this->assertEquals( + $expectedResults['minimalPrice'], + $priceInfo->getPrice($priceCode)->getMinimalPrice()->getValue(), + 'Failed to check minimal price on product' + ); + $this->assertEquals( + $expectedResults['maximalPrice'], + $priceInfo->getPrice($priceCode)->getMaximalPrice()->getValue(), + 'Failed to check maximal price on product' + ); + + $priceInfoFromIndexer = $this->productCollectionFactory->create() + ->addFieldToFilter('sku', 'bundle_product') + ->addPriceData() + ->load() + ->getFirstItem(); + + $this->assertEquals($expectedResults['minimalPrice'], $priceInfoFromIndexer->getMinimalPrice()); + $this->assertEquals($expectedResults['maximalPrice'], $priceInfoFromIndexer->getMaxPrice()); + } + + /** + * Test cases for current test + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function getTestCases() + { + return [ + '#1 Testing price for fixed bundle product with one simple' => [ + 'strategy' => $this->getProductWithOneSimple(), + 'expectedResults' => [ + // 110 + 10 (price from simple1) + 'minimalPrice' => 120, + // 110 + 10 (sum of simple price) + 'maximalPrice' => 120, + ] + ], + + '#2 Testing price for fixed bundle product with three simples and different qty' => [ + 'strategy' => $this->getProductWithDifferentQty(), + 'expectedResults' => [ + // 110 + 10 (min price from simples) + 'minimalPrice' => 120, + // 110 + (3 * 10) + (2 * 10) + 10 + 'maximalPrice' => 170, + ] + ], + + '#3 Testing price for fixed bundle product with three simples and different price' => [ + 'strategy' => $this->getProductWithDifferentPrice(), + 'expectedResults' => [ + // 110 + 10 + 'minimalPrice' => 120, + // 110 + 60 + 'maximalPrice' => 170, + ] + ], + + '#4 Testing price for fixed bundle product with three simples' => [ + 'strategy' => $this->getProductWithSamePrice(), + 'expectedResults' => [ + // 110 + 10 + 'minimalPrice' => 120, + // 110 + 30 + 'maximalPrice' => 140, + ] + ], + + ' + #5 Testing price for fixed bundle product + with fixed sub items, fixed options and without any discounts + ' => [ + 'strategy' => $this->getBundleConfiguration3( + LinkInterface::PRICE_TYPE_FIXED, + self::CUSTOM_OPTION_PRICE_TYPE_FIXED + ), + 'expectedResults' => [ + // 110 + 1 * 20 + 100 + 'minimalPrice' => 230, + + // 110 + 1 * 20 + 100 + 'maximalPrice' => 230, + ] + ], + + ' + #6 Testing price for fixed bundle product + with percent sub items, percent options and without any discounts + ' => [ + 'strategy' => $this->getBundleConfiguration3( + LinkInterface::PRICE_TYPE_PERCENT, + self::CUSTOM_OPTION_PRICE_TYPE_PERCENT + ), + 'expectedResults' => [ + // 110 + 110 * 0.2 + 110 * 1 + 'minimalPrice' => 242, + + // 110 + 110 * 0.2 + 110 * 1 + 'maximalPrice' => 242, + ] + ], + + ' + #7 Testing price for fixed bundle product + with fixed sub items, percent options and without any discounts + ' => [ + 'strategy' => $this->getBundleConfiguration3( + LinkInterface::PRICE_TYPE_FIXED, + self::CUSTOM_OPTION_PRICE_TYPE_PERCENT + ), + 'expectedResults' => [ + // 110 + 1 * 20 + 110 * 1 + 'minimalPrice' => 240, + + // 110 + 1 * 20 + 110 * 1 + 'maximalPrice' => 240, + ] + ], + + ' + #8 Testing price for fixed bundle product + with percent sub items, fixed options and without any discounts + ' => [ + 'strategy' => $this->getBundleConfiguration3( + LinkInterface::PRICE_TYPE_PERCENT, + self::CUSTOM_OPTION_PRICE_TYPE_FIXED + ), + 'expectedResults' => [ + // 110 + 110 * 0.2 + 100 + 'minimalPrice' => 232, + + // 110 + 110 * 0.2 + 100 + 'maximalPrice' => 232, + ] + ], + ]; + } + + /** + * Fixed bundle product with one simple + * @return array + */ + private function getProductWithOneSimple() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + ] + ], + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Fixed bundle product with three simples and different qty + * @return array + */ + private function getProductWithDifferentQty() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'price' => 10, + 'qty' => 3, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple2', + 'price' => 10, + 'qty' => 2, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple3', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Fixed bundle product with three simples and different price + * @return array + */ + private function getProductWithSamePrice() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple2', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple3', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ] + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Fixed bundle product with three simples + * @return array + */ + private function getProductWithDifferentPrice() + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'price' => 10, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple2', + 'price' => 20, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ], + [ + 'sku' => 'simple3', + 'price' => 30, + 'qty' => 1, + 'price_type' => LinkInterface::PRICE_TYPE_FIXED, + ] + ] + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + ]; + } + + /** + * Fixed bundle product with required option, custom option and without any discounts + * @param string $selectionsPriceType + * @param string $customOptionsPriceType + * @return array + */ + private function getBundleConfiguration3($selectionsPriceType, $customOptionsPriceType) + { + $optionsData = [ + [ + 'title' => 'Op1', + 'required' => true, + 'type' => 'checkbox', + 'links' => [ + [ + 'sku' => 'simple1', + 'qty' => 1, + 'price' => 20, + 'price_type' => $selectionsPriceType + ], + ] + ], + ]; + + $customOptionsData = [ + [ + 'price_type' => $customOptionsPriceType, + 'title' => 'Test Field', + 'type' => 'field', + 'is_require' => 1, + 'price' => 100, + 'sku' => '1-text', + ] + ]; + + return [ + [ + 'modifierName' => 'addSimpleProduct', + 'data' => [$optionsData] + ], + [ + 'modifierName' => 'addCustomOption', + 'data' => [$customOptionsData] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/OptionListTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/OptionListTest.php index 55f8821d7087b..47f50dc6d991e 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/OptionListTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/OptionListTest.php @@ -21,6 +21,9 @@ class OptionListTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * Set up + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -28,6 +31,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation disabled */ public function testGetItems() { diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceWithDimensionTest.php new file mode 100644 index 0000000000000..bc25c3fa29381 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/PriceWithDimensionTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Product; + +/** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @group indexer_dimension + * @magentoDataFixture Magento/Bundle/_files/product_with_tier_pricing.php + */ +class PriceWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Bundle\Model\Product\Price + */ + protected $_model; + + /** + * Set up + */ + protected function setUp() + { + $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Bundle\Model\Product\Price::class + ); + } + + /** + * Get tier price + */ + public function testGetTierPrice() + { + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $product = $productRepository->get('bundle-product'); + // fixture + + // Note that this is really not the "tier price" but the "tier discount percentage" + // so it is expected to be increasing instead of decreasing + $this->assertEquals(8.0, $this->_model->getTierPrice(2, $product)); + $this->assertEquals(20.0, $this->_model->getTierPrice(3, $product)); + $this->assertEquals(20.0, $this->_model->getTierPrice(4, $product)); + $this->assertEquals(30.0, $this->_model->getTierPrice(5, $product)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php index 9654df29bcb46..4303577e6c435 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php @@ -17,12 +17,16 @@ use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogInventory\Model\Stock; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Entity; use Magento\TestFramework\Helper\Bootstrap; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ProductTest extends \PHPUnit\Framework\TestCase { /** @@ -35,6 +39,9 @@ class ProductTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @return void + */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); @@ -43,6 +50,13 @@ protected function setUp() $this->model->setTypeId(Type::TYPE_BUNDLE); } + /** + * Tests Retrieve ans set type instance of the product + * + * @see \Magento\Catalog\Model\Product::getTypeInstance + * @see \Magento\Catalog\Model\Product::setTypeInstance + * @return void + */ public function testGetSetTypeInstance() { // model getter @@ -82,6 +96,12 @@ public function testCRUD() $crud->testCrud(); } + /** + * Tests Get product price model + * + * @see \Magento\Catalog\Model\Product::getPriceModel + * @return void + */ public function testGetPriceModel() { $this->model->setTypeId(Type::TYPE_BUNDLE); @@ -90,6 +110,12 @@ public function testGetPriceModel() $this->assertSame($type, $this->model->getPriceModel()); } + /** + * Tests Check is product composite + * + * @see \Magento\Catalog\Model\Product::isComposite + * @return void + */ public function testIsComposite() { $this->assertTrue($this->model->isComposite()); @@ -124,4 +150,122 @@ public function testMultipleStores() self::assertEquals($store->getId(), $updatedBundle->getStoreId()); } + + /** + * @param float $selectionQty + * @param float $qty + * @param int $isInStock + * @param bool $manageStock + * @param int $backorders + * @param bool $isSalable + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Bundle/_files/product.php + * @dataProvider stockConfigDataProvider + * @covers \Magento\Catalog\Model\Product::isSalable + */ + public function testIsSalable( + float $selectionQty, + float $qty, + int $isInStock, + bool $manageStock, + int $backorders, + bool $isSalable + ) { + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + $child = $productRepository->get('simple'); + $childStockItem = $child->getExtensionAttributes()->getStockItem(); + $childStockItem->setQty($qty); + $childStockItem->setIsInStock($isInStock); + $childStockItem->setUseConfigManageStock(false); + $childStockItem->setManageStock($manageStock); + $childStockItem->setUseConfigBackorders(false); + $childStockItem->setBackorders($backorders); + $productRepository->save($child); + + /** @var \Magento\Catalog\Model\Product $bundle */ + $bundle = $productRepository->get('bundle-product'); + foreach ($bundle->getExtensionAttributes()->getBundleProductOptions() as $productOption) { + foreach ($productOption->getProductLinks() as $productLink) { + $productLink->setCanChangeQuantity(0); + $productLink->setQty($selectionQty); + } + } + $productRepository->save($bundle); + + $this->assertEquals($isSalable, $bundle->isSalable()); + } + + /** + * @return array + */ + public function stockConfigDataProvider(): array + { + $qtyVars = [0, 10]; + $isInStockVars = [ + Stock::STOCK_OUT_OF_STOCK, + Stock::STOCK_IN_STOCK, + ]; + $manageStockVars = [false, true]; + $backordersVars = [ + Stock::BACKORDERS_NO, + Stock::BACKORDERS_YES_NONOTIFY, + Stock::BACKORDERS_YES_NOTIFY, + ]; + $selectionQtyVars = [5, 10, 15]; + + $variations = []; + foreach ($qtyVars as $qty) { + foreach ($isInStockVars as $isInStock) { + foreach ($manageStockVars as $manageStock) { + foreach ($backordersVars as $backorders) { + foreach ($selectionQtyVars as $selectionQty) { + $variationName = "selectionQty: {$selectionQty}" + . " qty: {$qty}" + . " isInStock: {$isInStock}" + . " manageStock: {$manageStock}" + . " backorders: {$backorders}"; + $isSalable = $this->checkIsSalable( + $selectionQty, + $qty, + $isInStock, + $manageStock, + $backorders + ); + + $variations[$variationName] = [ + $selectionQty, + $qty, + $isInStock, + $manageStock, + $backorders, + $isSalable + ]; + } + } + } + } + } + + return $variations; + } + + /** + * @param float $selectionQty + * @param float $qty + * @param int $isInStock + * @param bool $manageStock + * @param int $backorders + * @return bool + * @see \Magento\Bundle\Model\ResourceModel\Selection\Collection::addQuantityFilter + */ + private function checkIsSalable( + float $selectionQty, + float $qty, + int $isInStock, + bool $manageStock, + int $backorders + ): bool { + return !$manageStock || ($isInStock && ($backorders || $selectionQty <= $qty)); + } } diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php index 52abd7d6ff8eb..662b69c89bc6d 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/multiple_products.php @@ -28,7 +28,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData([ 'use_config_manage_stock' => 1, 'qty' => 100, @@ -59,7 +59,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData([ 'use_config_manage_stock' => 1, 'qty' => 50, @@ -85,7 +85,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData([ 'use_config_manage_stock' => 1, 'qty' => 140, @@ -116,7 +116,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData([ 'use_config_manage_stock' => 1, 'qty' => 20, @@ -147,7 +147,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData([ 'use_config_manage_stock' => 1, 'qty' => 15, diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_tier_pricing_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_tier_pricing_rollback.php new file mode 100644 index 0000000000000..7e4c5593f18bb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product_with_tier_pricing_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/* + * Since the bundle product creation GUI doesn't allow to choose values for bundled products' custom options, + * bundled items should not contain products with required custom options. + * However, if to create such a bundle product, it will be always out of stock. + */ +require __DIR__ . '/../../../Magento/Catalog/_files/products_rollback.php'; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $product = $productRepository->get('bundle-product'); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php index 53e8281ffbdf1..c26f5860f2375 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php @@ -33,6 +33,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoDbIsolation disabled * * @return void */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php index cc04e48adb620..ec2e97f942451 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Product/CompareTest.php @@ -22,6 +22,9 @@ class CompareTest extends \Magento\TestFramework\TestCase\AbstractController */ protected $productRepository; + /** + * Test setup + */ protected function setUp() { parent::setUp(); @@ -32,6 +35,9 @@ protected function setUp() $this->productRepository = $objectManager->create(\Magento\Catalog\Model\ProductRepository::class); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ public function testAddAction() { $this->_requireVisitorWithNoProducts(); @@ -48,7 +54,12 @@ public function testAddAction() ); $this->assertSessionMessages( - $this->equalTo(['You added product Simple Product 1 Name to the comparison list.']), + $this->equalTo( + [ + 'You added product Simple Product 1 Name to the '. + '<a href="http://localhost/index.php/catalog/product_compare/">comparison list</a>.' + ] + ), MessageInterface::TYPE_SUCCESS ); @@ -57,17 +68,23 @@ public function testAddAction() $this->_assertCompareListEquals([$product->getEntityId()]); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ public function testIndexActionAddProducts() { $this->_requireVisitorWithNoProducts(); $product = $this->productRepository->get('simple_product_2'); $this->dispatch('catalog/product_compare/index/items/' . $product->getEntityId()); - $this->assertRedirect($this->equalTo('http://localhost/index.php/catalog/product_compare/index/')); + $this->assertRedirect($this->stringStartsWith('http://localhost/index.php/catalog/product_compare/index/')); $this->_assertCompareListEquals([$product->getEntityId()]); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ public function testRemoveAction() { $this->_requireVisitorWithTwoProducts(); @@ -84,6 +101,9 @@ public function testRemoveAction() $this->_assertCompareListEquals([$restProduct->getEntityId()]); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ public function testRemoveActionWithSession() { $this->_requireCustomerWithTwoProducts(); @@ -101,6 +121,9 @@ public function testRemoveActionWithSession() $this->_assertCompareListEquals([$secondProduct->getEntityId()]); } + /** + * testIndexActionDisplay + */ public function testIndexActionDisplay() { $this->_requireVisitorWithTwoProducts(); @@ -127,6 +150,9 @@ public function testIndexActionDisplay() $this->assertContains('$987.65', $responseBody); } + /** + * testClearAction + */ public function testClearAction() { $this->_requireVisitorWithTwoProducts(); @@ -160,6 +186,9 @@ public function testRemoveActionProductNameXss() ); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ protected function _prepareCompareListWithProductNameXss() { /** @var $visitor \Magento\Customer\Model\Visitor */ @@ -182,6 +211,9 @@ protected function _prepareCompareListWithProductNameXss() ); } + /** + * _requireVisitorWithNoProducts + */ protected function _requireVisitorWithNoProducts() { /** @var $visitor \Magento\Customer\Model\Visitor */ @@ -201,6 +233,9 @@ protected function _requireVisitorWithNoProducts() $this->_assertCompareListEquals([]); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ protected function _requireVisitorWithTwoProducts() { /** @var $visitor \Magento\Customer\Model\Visitor */ @@ -233,6 +268,9 @@ protected function _requireVisitorWithTwoProducts() $this->_assertCompareListEquals([$firstProductEntityId, $secondProductEntityId]); } + /** + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ protected function _requireCustomerWithTwoProducts() { $customer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php index bcb29fa002f01..5a4dadedb1c9e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Helper/OutputTest.php @@ -64,6 +64,24 @@ public function testCategoryAttribute() ); } + /** + * @dataProvider isDirectiveDataProvider + */ + public function testIsDirective($html, $expectedResult) + { + $this->assertEquals($expectedResult, $this->_helper->isDirectivesExists($html)); + } + + public function isDirectiveDataProvider() + { + return [ + ['{{', false], + ['Test string', false], + ['{store url="customer/account/login"}', false], + ['{{store url="customer/account/login"}}', true], + ]; + } + /** * Helper method for testProcess() * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowTest.php index 2b7c416f6be16..15c90891878a0 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowTest.php @@ -61,12 +61,13 @@ public function testProductUpdate() $this->_processor->getIndexer()->isScheduled(), 'Indexer is in scheduled mode when turned to update on save mode' ); - $this->_processor->reindexAll(); $this->_product->load(1); $this->_product->setName('Updated Product'); $this->_product->save(); + $this->_processor->reindexAll(); + $category = $categoryFactory->create()->load(9); $layer = $listProduct->getLayer(); $layer->setCurrentCategory($category); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php new file mode 100644 index 0000000000000..691ae78de71af --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Price/SimpleWithOptionsTierPriceWithDimensionTest.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Api\ScopedProductTierPriceManagementInterface; +use Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Pricing\Price\TierPrice; +use Magento\Customer\Model\Group; + +/** + * @group indexer_dimension + */ +class SimpleWithOptionsTierPriceWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var CollectionFactory + */ + private $productCollectionFactory; + + /** + * set up + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->productCollectionFactory = $this->objectManager->create(CollectionFactory::class); + } + + /** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @magentoDataFixture Magento/Catalog/_files/category_product.php + */ + public function testTierPrice() + { + $tierPriceValue = 9.00; + + $tierPrice = $this->objectManager->create(ProductTierPriceInterfaceFactory::class) + ->create(); + $tierPrice->setCustomerGroupId(Group::CUST_GROUP_ALL); + $tierPrice->setQty(1.00); + $tierPrice->setValue($tierPriceValue); + $tierPriceManagement = $this->objectManager->create(ScopedProductTierPriceManagementInterface::class); + $tierPriceManagement->add('simple333', $tierPrice); + + $productCollection = $this->productCollectionFactory->create(); + $productCollection->addIdFilter(333); + $productCollection->addPriceData(); + $productCollection->load(); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $productCollection->getFirstItem(); + $tierPrice = $product->getPriceInfo() + ->getPrice(TierPrice::PRICE_CODE) + ->getValue(); + + $this->assertEquals($tierPriceValue, $tierPrice); + + $tierPrice = $product->getTierPrice(1); + $this->assertEquals($tierPriceValue, $tierPrice); + + $tierPrices = $product->getData('tier_price'); + $this->assertEquals($tierPriceValue, $tierPrices[0]['price']); + + $minPrice = $product->getData('min_price'); + $this->assertEquals($tierPriceValue, $minPrice); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfoTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfoTest.php index 41556d5558006..d4214a32f31dc 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfoTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfoTest.php @@ -30,6 +30,9 @@ class ValidatorInfoTest extends \PHPUnit\Framework\TestCase */ protected $validateFactoryMock; + /** + * {@inheritdoc} + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -111,11 +114,11 @@ public function testExceptionWithoutErrors() */ public function testValidate() { - $validateMock = $this->createPartialMock(\Zend_Validate::class, ['isValid']); - $validateMock->expects($this->once())->method('isValid')->will($this->returnValue(true)); + //use actual zend class to test changed functionality + $validate = $this->objectManager->create(\Zend_Validate::class); $this->validateFactoryMock->expects($this->once()) ->method('create') - ->will($this->returnValue($validateMock)); + ->will($this->returnValue($validate)); $this->assertTrue( $this->model->validate( $this->getOptionValue(), diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceWithDimensionTest.php new file mode 100644 index 0000000000000..280e83a863325 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/PriceWithDimensionTest.php @@ -0,0 +1,182 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\Type; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ProductRepository; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DataObject; +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @group indexer_dimension + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ +class PriceWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Catalog\Model\Product\Type\Price + */ + protected $_model; + + /** + * Set up + */ + protected function setUp() + { + $this->_model = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\Product\Type\Price::class + ); + } + + /** + * Get price from indexer + */ + public function testGetPriceFromIndexer() + { + /** @var PriceTableResolver $tableResolver */ + $tableResolver = Bootstrap::getObjectManager()->create(PriceTableResolver::class); + + /** @var ResourceConnection $resourceConnection */ + $resourceConnection = Bootstrap::getObjectManager()->create(ResourceConnection::class); + + /** @var DimensionFactory $dimensionFactory */ + $dimensionFactory = Bootstrap::getObjectManager()->create(DimensionFactory::class); + $dimension = [ + $dimensionFactory->create(CustomerGroupDimensionProvider::DIMENSION_NAME, (string)0), + $dimensionFactory->create(WebsiteDimensionProvider::DIMENSION_NAME, (string)1) + ]; + $connection = $resourceConnection->getConnection(); + $priceTable = $connection->getTableName( + $tableResolver->resolve('catalog_product_index_price', $dimension) + ); + + $select = $connection->select()->from($priceTable)->where('entity_id = 1'); + + $return = $connection->fetchAll($select); + + $this->assertEquals('10', $return[0]['price']); + $this->assertEquals('10', $return[0]['final_price']); + $this->assertEquals('19', $return[0]['min_price']); + $this->assertEquals('19', $return[0]['max_price']); + } + + /** + * Get price + */ + public function testGetPrice() + { + $this->assertEquals('test', $this->_model->getPrice(new DataObject(['price' => 'test']))); + } + + /** + * Get final price + */ + public function testGetFinalPrice() + { + $repository = Bootstrap::getObjectManager()->create( + ProductRepository::class + ); + $product = $repository->get('simple'); + // fixture + + // regular & tier prices + $this->assertEquals(10.0, $this->_model->getFinalPrice(1, $product)); + $this->assertEquals(8.0, $this->_model->getFinalPrice(2, $product)); + $this->assertEquals(5.0, $this->_model->getFinalPrice(5, $product)); + + // with options + $buyRequest = $this->prepareBuyRequest($product); + $product->getTypeInstance()->prepareForCart($buyRequest, $product); + + //product price + options price(10+1+2+3+3) + $this->assertEquals(19.0, $this->_model->getFinalPrice(1, $product)); + + //product tier price + options price(5+1+2+3+3) + $this->assertEquals(14.0, $this->_model->getFinalPrice(5, $product)); + } + + /** + * Get formated price + */ + public function testGetFormatedPrice() + { + $repository = Bootstrap::getObjectManager()->create( + ProductRepository::class + ); + $product = $repository->get('simple'); + // fixture + $this->assertEquals('<span class="price">$10.00</span>', $this->_model->getFormatedPrice($product)); + } + + /** + * Calculate price + */ + public function testCalculatePrice() + { + $this->assertEquals(10, $this->_model->calculatePrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01')); + $this->assertEquals(8, $this->_model->calculatePrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01')); + } + + /** + * Calculate special price + */ + public function testCalculateSpecialPrice() + { + $this->assertEquals( + 10, + $this->_model->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '1971-01-01 01:01:01') + ); + $this->assertEquals( + 8, + $this->_model->calculateSpecialPrice(10, 8, '1970-12-12 23:59:59', '2034-01-01 01:01:01') + ); + } + + /** + * Is tier price fixed + */ + public function testIsTierPriceFixed() + { + $this->assertTrue($this->_model->isTierPriceFixed()); + } + + /** + * Build buy request based on product custom options + * + * @param Product $product + * @return DataObject + */ + private function prepareBuyRequest(Product $product) + { + $options = []; + /** @var $option \Magento\Catalog\Model\Product\Option */ + foreach ($product->getOptions() as $option) { + switch ($option->getGroupByType()) { + case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_GROUP_DATE: + $value = ['year' => 2013, 'month' => 8, 'day' => 9, 'hour' => 13, 'minute' => 35]; + break; + case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_GROUP_SELECT: + $value = key($option->getValues()); + break; + default: + $value = 'test'; + break; + } + $options[$option->getId()] = $value; + } + + return new DataObject(['qty' => 1, 'options' => $options]); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceTest.php index 594133e984a46..7b7a0591d1d97 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceTest.php @@ -8,6 +8,7 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogInventory\Api\StockRegistryInterface; /** * Tests product model: @@ -23,11 +24,23 @@ class ProductPriceTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @inheritdoc + */ protected function setUp() { $this->_model = Bootstrap::getObjectManager()->create(Product::class); + $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); } + /** + * @return void + */ public function testGetPrice() { $this->assertEmpty($this->_model->getPrice()); @@ -35,6 +48,9 @@ public function testGetPrice() $this->assertEquals(10.0, $this->_model->getPrice()); } + /** + * @return void + */ public function testGetPriceModel() { $default = $this->_model->getPriceModel(); @@ -66,6 +82,9 @@ public function testGetFormatedPrice() $this->assertEquals('<span class="price">$0.00</span>', $this->_model->getFormatedPrice()); } + /** + * @return void + */ public function testSetGetFinalPrice() { $this->assertEquals(0, $this->_model->getFinalPrice()); @@ -81,14 +100,37 @@ public function testSetGetFinalPrice() */ public function testGetMinPrice(): void { - $productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); - $product = $productRepository->get('simple'); + $product = $this->productRepository->get('simple'); $collection = Bootstrap::getObjectManager()->create(Collection::class); $collection->addIdFilter($product->getId()); $collection->addPriceData(); $collection->load(); /** @var \Magento\Catalog\Model\Product $product */ $product = $collection->getFirstItem(); - $this->assertEquals(333, $product->getData('min_price')); + $this->assertEquals(323, $product->getData('min_price')); + } + + /** + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php + */ + public function testGetMinPriceForComposite(): void + { + $confProduct = $this->productRepository->get('configurable'); + $collection = Bootstrap::getObjectManager()->create(Collection::class); + $collection->addIdFilter($confProduct->getId()); + $collection->addPriceData(); + $collection->load(); + $product = $collection->getFirstItem(); + $this->assertEquals(10, $product->getData('min_price')); + + $childProduct = $this->productRepository->get('simple_10'); + $stockRegistry = Bootstrap::getObjectManager()->get(StockRegistryInterface::class); + $stockItem = $stockRegistry->getStockItem($childProduct->getId()); + $stockItem->setIsInStock(false); + $stockRegistry->updateStockItemBySku($childProduct->getSku(), $stockItem); + $collection->clear()->load(); + $product = $collection->getFirstItem(); + $this->assertEquals(20, $product->getData('min_price')); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceWithDimensionTest.php new file mode 100644 index 0000000000000..348d6e19b44e3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductPriceWithDimensionTest.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\CatalogInventory\Api\StockRegistryInterface; + +/** + * Tests product model: + * - pricing behaviour is tested + * @group indexer_dimension + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @see \Magento\Catalog\Model\ProductTest + * @see \Magento\Catalog\Model\ProductExternalTest + */ +class ProductPriceWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Catalog\Model\Product + */ + protected $_model; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * Set up + */ + protected function setUp() + { + $this->_model = Bootstrap::getObjectManager()->create(Product::class); + $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); + } + + /** + * Get price + */ + public function testGetPrice() + { + $this->assertEmpty($this->_model->getPrice()); + $this->_model->setPrice(10.0); + $this->assertEquals(10.0, $this->_model->getPrice()); + } + + /** + * Get price model + */ + public function testGetPriceModel() + { + $default = $this->_model->getPriceModel(); + $this->assertInstanceOf(\Magento\Catalog\Model\Product\Type\Price::class, $default); + $this->assertSame($default, $this->_model->getPriceModel()); + } + + /** + * See detailed tests at \Magento\Catalog\Model\Product\Type*_PriceTest + */ + public function testGetTierPrice() + { + $this->assertEquals([], $this->_model->getTierPrice()); + } + + /** + * See detailed tests at \Magento\Catalog\Model\Product\Type*_PriceTest + */ + public function testGetTierPriceCount() + { + $this->assertEquals(0, $this->_model->getTierPriceCount()); + } + + /** + * See detailed tests at \Magento\Catalog\Model\Product\Type*_PriceTest + */ + public function testGetFormatedPrice() + { + $this->assertEquals('<span class="price">$0.00</span>', $this->_model->getFormatedPrice()); + } + + /** + * Set get final price + */ + public function testSetGetFinalPrice() + { + $this->assertEquals(0, $this->_model->getFinalPrice()); + $this->_model->setPrice(10); + $this->_model->setFinalPrice(10); + $this->assertEquals(10, $this->_model->getFinalPrice()); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_with_options.php + * @return void + */ + public function testGetMinPrice(): void + { + $product = $this->productRepository->get('simple'); + $collection = Bootstrap::getObjectManager()->create(Collection::class); + $collection->addIdFilter($product->getId()); + $collection->addPriceData(); + $collection->load(); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $collection->getFirstItem(); + $this->assertEquals(323, $product->getData('min_price')); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php + */ + public function testGetMinPriceForComposite() + { + $confProduct = $this->productRepository->get('configurable'); + $collection = Bootstrap::getObjectManager()->create(Collection::class); + $collection->addIdFilter($confProduct->getId()); + $collection->addPriceData(); + $collection->load(); + $product = $collection->getFirstItem(); + $this->assertEquals(10, $product->getData('min_price')); + + $childProduct = $this->productRepository->get('simple_10'); + $stockRegistry = Bootstrap::getObjectManager()->get(StockRegistryInterface::class); + $stockItem = $stockRegistry->getStockItem($childProduct->getId()); + $stockItem->setIsInStock(false); + $stockRegistry->updateStockItemBySku($childProduct->getSku(), $stockItem); + $collection->clear()->load(); + $product = $collection->getFirstItem(); + $this->assertEquals(20, $product->getData('min_price')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index b00090850e09b..82d39bbb7066a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -20,6 +20,8 @@ * @magentoDbIsolation enabled * @magentoAppIsolation enabled * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyMethods) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class ProductTest extends \PHPUnit\Framework\TestCase { @@ -33,6 +35,9 @@ class ProductTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * @inheritdoc + */ protected function setUp() { $this->productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() @@ -43,6 +48,10 @@ protected function setUp() ); } + /** + * @throws \Magento\Framework\Exception\FileSystemException + * @return void + */ public static function tearDownAfterClass() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -64,6 +73,9 @@ public static function tearDownAfterClass() } } + /** + * @return void + */ public function testCanAffectOptions() { $this->assertFalse($this->_model->canAffectOptions()); @@ -103,6 +115,9 @@ public function testCRUD() $crud->testCrud(); } + /** + * @return void + */ public function testCleanCache() { \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( @@ -123,6 +138,9 @@ public function testCleanCache() ); } + /** + * @return void + */ public function testAddImageToMediaGallery() { // Model accepts only files in tmp media path, we need to copy fixture file there @@ -330,6 +348,9 @@ public function testIsVirtual() $this->assertTrue($model->getIsVirtual()); } + /** + * @return void + */ public function testToArray() { $this->assertEquals([], $this->_model->toArray()); @@ -337,6 +358,9 @@ public function testToArray() $this->assertEquals(['sku' => 'sku', 'name' => 'name'], $this->_model->toArray()); } + /** + * @return void + */ public function testFromArray() { $this->_model->fromArray(['sku' => 'sku', 'name' => 'name', 'stock_item' => ['key' => 'value']]); @@ -408,6 +432,9 @@ public function testIsProductsHasSku() ); } + /** + * @return void + */ public function testProcessBuyRequest() { $request = new \Magento\Framework\DataObject(); @@ -416,6 +443,9 @@ public function testProcessBuyRequest() $this->assertArrayHasKey('errors', $result->getData()); } + /** + * @return void + */ public function testValidate() { $this->_model->setTypeId( @@ -534,6 +564,8 @@ public function testValidateUniqueInputAttributeOnTheSameProduct() } /** + * Tests Customizable Options price values including negative value. + * * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_options.php * @magentoAppIsolation enabled */ @@ -543,7 +575,7 @@ public function testGetOptions() $options = $this->_model->getOptions(); $this->assertNotEmpty($options); $expectedValue = [ - '3-1-select' => 3000.00, + '3-1-select' => -3000.00, '3-2-select' => 5000.00, '4-1-radio' => 600.234, '4-2-radio' => 40000.00 diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index e58d445a25650..a5e55e181cbaf 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -50,6 +50,7 @@ public function testAddPriceDataOnSchedule() { $this->processor->getIndexer()->setScheduled(true); $this->assertTrue($this->processor->getIndexer()->isScheduled()); + $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ @@ -81,7 +82,6 @@ public function testAddPriceDataOnSchedule() $product = reset($items); $this->assertCount(2, $items); $this->assertEquals(15, $product->getPrice()); - $this->processor->getIndexer()->reindexList([1]); $this->processor->getIndexer()->setScheduled(false); } @@ -127,6 +127,42 @@ public function testGetProductsWithTierPrice() $this->assertEquals(5, $tierPrices[2]->getValue()); } + /** + * Test addAttributeToSort() with attribute 'is_saleable' works properly on frontend. + * + * @dataProvider addAttributeToSortDataProvider + * @magentoDataFixture Magento/Catalog/_files/multiple_products_with_non_saleable_product.php + * @magentoConfigFixture current_store cataloginventory/options/show_out_of_stock 1 + * @magentoAppIsolation enabled + * @magentoAppArea frontend + */ + public function testAddAttributeToSort(string $productSku, string $order) + { + /** @var Collection $productCollection */ + $this->collection->addAttributeToSort('is_saleable', $order); + self::assertEquals(2, $this->collection->count()); + self::assertSame($productSku, $this->collection->getFirstItem()->getSku()); + } + + /** + * Provide test data for testAddAttributeToSort(). + * + * @return array + */ + public function addAttributeToSortDataProvider() + { + return [ + [ + 'product_sku' => 'simple_saleable', + 'order' => Collection::SORT_ORDER_DESC, + ], + [ + 'product_sku' => 'simple_not_saleable', + 'order' => Collection::SORT_ORDER_ASC, + ] + ]; + } + /** * Checks a case if table for join specified as an array. * @@ -151,20 +187,4 @@ public function testJoinTable() self::assertContains($expected, str_replace(PHP_EOL, '', $sql)); } - - /** - * Checks that a collection uses the correct join when filtering by null. - * - * This actually affects AbstractCollection, but inheritance yada yada. - * - * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php - * @magentoDbIsolation enabled - */ - public function testFilterByNull() - { - $this->collection->addAttributeToFilter([['attribute' => 'special_price', 'null' => true]]); - $productCount = $this->collection->count(); - - $this->assertEquals(1, $productCount, 'Product with null special_price not found'); - } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/website_attribute_sync.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/website_attribute_sync.php index 9c55370156235..96a537f123d92 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/website_attribute_sync.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/website_attribute_sync.php @@ -126,7 +126,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(AttributeStatus::STATUS_ENABLED) ->setWebsiteIds([$website->getId()]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $product = $productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php index c6d6c3cf42214..8e5b38a4f8802 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php @@ -68,6 +68,9 @@ class FinalPriceBoxTest extends \PHPUnit\Framework\TestCase */ private $templateEnginePool; + /** + * Set up + */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); @@ -125,7 +128,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Catalog/_files/product_has_tier_price_show_as_low_as.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testRenderAmountMinimalProductWithTierPricesShouldShowMinTierPrice() @@ -136,7 +139,7 @@ public function testRenderAmountMinimalProductWithTierPricesShouldShowMinTierPri /** * @magentoDataFixture Magento/Catalog/_files/product_different_store_prices.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1 */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_duplicates.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_duplicates.php index 2873ae7ccf399..d3825675b7244 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/category_duplicates.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/category_duplicates.php @@ -48,7 +48,7 @@ \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED )->setWebsiteIds( [1] -)->setCateroryIds( +)->setCategoryIds( [] )->setStockData( ['qty' => 100, 'is_in_stock' => 1, 'manage_stock' => 1] diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_mixed_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_mixed_products.php index 9f2045db66108..7d05f30337cc2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_mixed_products.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_mixed_products.php @@ -186,7 +186,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('5.99') ->save(); @@ -211,7 +211,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('15.99') ->save(); @@ -231,7 +231,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('25.99') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products.php index fa6e826eec70e..dcdbed7562fdb 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products.php @@ -25,7 +25,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('5.99') ->save(); @@ -50,7 +50,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('15.99') ->save(); @@ -70,7 +70,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('25.99') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_few_out_of_stock.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_few_out_of_stock.php index 4235fcf11f9f8..13b9870f24f66 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_few_out_of_stock.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_few_out_of_stock.php @@ -53,7 +53,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setCategoryIds([300]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 0, 'is_qty_decimal' => 0, 'is_in_stock' => 0]) ->setSpecialPrice('5.99') diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product.php new file mode 100644 index 0000000000000..30b2aad2317c4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName('Simple Product') + ->setSku('simple_saleable') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_IN_CART) + ->setPrice(10) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->save(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName('Simple Product2') + ->setSku('simple_not_saleable') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_ON_GESTURE) + ->setPrice(20) + ->setWeight(1) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 0]) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product_rollback.php new file mode 100644 index 0000000000000..220509698dc33 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_products_with_non_saleable_product_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +foreach (['simple_saleable', 'simple_not_saleable'] as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php index 6ab607b5de88f..7dacdc42463a9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php @@ -12,45 +12,48 @@ $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class ); -$attribute->setData( - [ - 'attribute_code' => 'multiselect_attribute', - 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), - 'is_global' => 1, - 'is_user_defined' => 1, - 'frontend_input' => 'multiselect', - 'is_unique' => 0, - 'is_required' => 0, - 'is_searchable' => 0, - 'is_visible_in_advanced_search' => 0, - 'is_comparable' => 0, - 'is_filterable' => 1, - 'is_filterable_in_search' => 0, - 'is_used_for_promo_rules' => 0, - 'is_html_allowed_on_front' => 1, - 'is_visible_on_front' => 0, - 'used_in_product_listing' => 0, - 'used_for_sort_by' => 0, - 'frontend_label' => ['Multiselect Attribute'], - 'backend_type' => 'varchar', - 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, - 'option' => [ - 'value' => [ - 'option_1' => ['Option 1'], - 'option_2' => ['Option 2'], - 'option_3' => ['Option 3'], - 'option_4' => ['Option 4 "!@#$%^&*'] +$entityType = $installer->getEntityTypeId('catalog_product'); +if (!$attribute->loadByCode($entityType, 'multiselect_attribute')->getAttributeId()) { + $attribute->setData( + [ + 'attribute_code' => 'multiselect_attribute', + 'entity_type_id' => $entityType, + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'multiselect', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 0, + 'is_filterable' => 1, + 'is_filterable_in_search' => 0, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Multiselect Attribute'], + 'backend_type' => 'varchar', + 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, + 'option' => [ + 'value' => [ + 'option_1' => ['Option 1'], + 'option_2' => ['Option 2'], + 'option_3' => ['Option 3'], + 'option_4' => ['Option 4 "!@#$%^&*'] + ], + 'order' => [ + 'option_1' => 1, + 'option_2' => 2, + 'option_3' => 3, + 'option_4' => 4, + ], ], - 'order' => [ - 'option_1' => 1, - 'option_2' => 2, - 'option_3' => 3, - 'option_4' => 4, - ], - ], - ] -); -$attribute->save(); + ] + ); + $attribute->save(); -/* Assign attribute to attribute set */ -$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php new file mode 100644 index 0000000000000..3d2dfcdff53e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** + * Create multiselect attribute + */ +require __DIR__ . '/multiselect_attribute.php'; + +/** Create product with options out of stock and multiselect attribute */ + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); + +/** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ +$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class +); +$options->setAttributeFilter($attribute->getId()); +$optionIds = $options->getAllIds(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($optionIds[1] * 20) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setWebsiteIds([1]) + ->setName('Out of Stock With Multiselect') + ->setSku('simple_ms_out_of_stock') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setMultiselectAttribute([$optionIds[1], $optionIds[2], $optionIds[3]]) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 0,'is_in_stock' => 0]) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute_rollback.php new file mode 100644 index 0000000000000..d1926f4769f77 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute_rollback.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/multiselect_attribute_rollback.php'; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Indexer\IndexerRegistry; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple_ms_out_of_stock', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { +} + +\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(IndexerRegistry::class) + ->get(Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID) + ->reindexAll(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php index c3abd2dfcdae7..059b784978a22 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php @@ -51,7 +51,7 @@ [ 'option_type_id' => null, 'title' => 'Option 1', - 'price' => '3,000.00', + 'price' => '-3,000.00', 'price_type' => 'fixed', 'sku' => '3-1-select', ], diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_options.php index a6e01370dfefb..a9bc557fd9b72 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_options.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_with_options.php @@ -11,8 +11,6 @@ $product->setTypeId( 'simple' -)->setId( - 1 )->setAttributeSetId( 4 )->setWebsiteIds( @@ -49,7 +47,7 @@ 'type' => 'field', 'is_require' => true, 'sort_order' => 1, - 'price' => 10.0, + 'price' => -10.0, 'price_type' => 'fixed', 'sku' => 'sku1', 'max_characters' => 10, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php index 091e64dbe6a4f..48c47c9988d59 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php @@ -87,7 +87,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('5.99') ->save(); @@ -112,7 +112,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('15.99') ->save(); @@ -132,7 +132,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('25.99') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute_rollback.php index 7a72f168f924f..eb8201f04e6cc 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_multiselect_attribute_rollback.php @@ -3,7 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + require __DIR__ . '/multiselect_attribute_rollback.php'; + +use Magento\Framework\Indexer\IndexerRegistry; + /** * Remove all products as strategy of isolation process */ @@ -22,3 +26,7 @@ $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); + +\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(IndexerRegistry::class) + ->get(Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID) + ->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/UploaderTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/UploaderTest.php index cf5cb69ebdc27..f61aa7578d4a3 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/UploaderTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/UploaderTest.php @@ -48,7 +48,14 @@ protected function setUp() $mediaPath = $appParams[DirectoryList::MEDIA][DirectoryList::PATH]; $this->directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); $tmpDir = $this->directory->getRelativePath($mediaPath . '/import'); - $this->uploader->setTmpDir($tmpDir); + if (!$this->directory->create($tmpDir)) { + throw new \RuntimeException('Failed to create temporary directory'); + } + if (!$this->uploader->setTmpDir($tmpDir)) { + throw new \RuntimeException( + 'Failed to set temporary directory for files.' + ); + } parent::setUp(); } @@ -70,6 +77,7 @@ public function testMoveWithValidFile(): void * @magentoAppIsolation enabled * @return void * @expectedException \Exception + * @expectedExceptionMessage Disallowed file type */ public function testMoveWithInvalidFile(): void { diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_two_stores.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_two_stores.csv index f343cd20ecc78..eda9f3a01bf55 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_two_stores.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_two_stores.csv @@ -1,4 +1,4 @@ product_websites,store_view_code,attribute_set_code,product_type,categories,sku,price,name,url_key -base,,Default,simple,Default Category/category-defaultstore,product,123,product,product +base,,Default,simple,Default Category/category-admin,product,123,product,product ,default,Default,simple,,product,,product-default,product-default ,fixturestore,Default,simple,,product,,product-fixture,product-fixture diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php index 330d9511dcdb9..5a6b1720f5a35 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php @@ -34,7 +34,7 @@ \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED )->setWebsiteIds( [1] -)->setCateroryIds( +)->setCategoryIds( [] )->setStockData( ['qty' => 100, 'is_in_stock' => 1, 'manage_stock' => 1] diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php index 873c5db9ab782..a849e412675ea 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/Quote/Item/QuantityValidatorTest.php @@ -58,6 +58,9 @@ class QuantityValidatorTest extends \PHPUnit\Framework\TestCase */ private $observer; + /** + * Set up + */ protected function setUp() { /** @var \Magento\Framework\ObjectManagerInterface objectManager */ @@ -83,7 +86,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Checkout/_files/quote_with_bundle_product.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testQuoteWithOptions() @@ -108,7 +111,7 @@ public function testQuoteWithOptions() /** * @magentoDataFixture Magento/Checkout/_files/quote_with_bundle_product.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testQuoteWithOptionsWithErrors() @@ -159,7 +162,7 @@ private function setMockStockStateResultToQuoteItemOptions($quoteItem, $resultMo * Gets \Magento\Quote\Model\Quote\Item from \Magento\Quote\Model\Quote by product id * * @param \Magento\Quote\Model\Quote $quote - * @param $productId + * @param int $productId * @return \Magento\Quote\Model\Quote\Item */ private function _getQuoteItemIdByProductId($quote, $productId) diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php index b1a10c894f83a..495d19a2745e5 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/Product/PriceTest.php @@ -3,8 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types=1); - namespace Magento\CatalogRule\Model\Indexer\Product; use Magento\TestFramework\Helper\Bootstrap; @@ -14,13 +12,6 @@ use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Api\SortOrder; -/** - * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/attribute.php - * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/product_with_attribute.php - * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/rule_by_attribute.php - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled - */ class PriceTest extends \PHPUnit\Framework\TestCase { /** @@ -28,18 +19,18 @@ class PriceTest extends \PHPUnit\Framework\TestCase */ private $resourceRule; - /** - * {@inheritdoc} - */ protected function setUp() { $this->resourceRule = Bootstrap::getObjectManager()->get(Rule::class); } /** - * @return void + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/configurable_product.php + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/rule_by_attribute.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled */ - public function testPriceApplying() : void + public function testPriceApplying() { $customerGroupId = 1; $websiteId = 1; @@ -52,7 +43,6 @@ public function testPriceApplying() : void /** @var \Magento\Catalog\Model\Product $simpleProduct */ $simpleProduct = $collection->getFirstItem(); $simpleProduct->setPriceCalculation(false); - $rulePrice = $this->resourceRule->getRulePrice(new \DateTime(), $websiteId, $customerGroupId, $simpleProductId); $this->assertEquals($rulePrice, $simpleProduct->getFinalPrice()); @@ -63,16 +53,17 @@ public function testPriceApplying() : void $collection->load(); /** @var \Magento\Catalog\Model\Product $confProduct */ $confProduct = $collection->getFirstItem(); - - $this->assertEquals($simpleProduct->getMinimalPrice(), $confProduct->getMinimalPrice()); + $this->assertEquals($simpleProduct->getFinalPrice(), $confProduct->getMinimalPrice()); } /** + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/simple_products.php + * @magentoDataFixtureBeforeTransaction Magento/CatalogRule/_files/rule_by_attribute.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled * @magentoAppArea frontend - * - * @return void */ - public function testSortByPrice() : void + public function testSortByPrice() { $searchCriteria = Bootstrap::getObjectManager()->create(SearchCriteriaInterface::class); $sortOrder = Bootstrap::getObjectManager()->create(SortOrder::class); @@ -80,10 +71,10 @@ public function testSortByPrice() : void $searchCriteria->setSortOrders([$sortOrder]); $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); $searchResults = $productRepository->getList($searchCriteria); - $products = $searchResults->getItems(); + /** @var \Magento\Catalog\Model\Product[] $products */ + $products = array_values($searchResults->getItems()); - /** @var \Magento\Catalog\Model\Product $product1 */ - $product1 = array_values($products)[0]; + $product1 = $products[0]; $product1->setPriceCalculation(false); $this->assertEquals('simple1', $product1->getSku()); $rulePrice = $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $product1->getId()); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/ProductRuleTest.php b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/ProductRuleTest.php index 76d17527d567a..911c7aa30641e 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/ProductRuleTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/Model/Indexer/ProductRuleTest.php @@ -67,7 +67,7 @@ public function testReindexWithProductNotVisibleIndividually() $this->assertEquals( 7.5, $this->resourceRule->getRulePrice(new \DateTime(), 1, 1, $product->getId()), - "Catalog price rule doesn't apply to to product with visibility value \"Not Visibility Individually\"" + "Catalog price rule doesn't apply to product with visibility value \"Not Visibility Individually\"" ); } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product.php similarity index 52% rename from dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute.php rename to dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product.php index 071f5d7d9fd00..ef7144d9d8438 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product.php @@ -6,12 +6,12 @@ declare(strict_types=1); require __DIR__ . '/../../ConfigurableProduct/_files/configurable_attribute.php'; +require __DIR__ . '/simple_products.php'; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $storeManager = $objectManager->get(\Magento\Store\Model\StoreManager::class); $store = $storeManager->getStore('default'); - $productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); $installer = $objectManager->get(\Magento\Catalog\Setup\CategorySetup::class); @@ -19,63 +19,22 @@ $attributeValues = []; $associatedProductIds = []; -/** @var Magento\Eav\Model\Entity\Attribute\Option[] $options */ +$attributeRepository = $objectManager->get(\Magento\Eav\Api\AttributeRepositoryInterface::class); +$attribute = $attributeRepository->get('catalog_product', 'test_configurable'); $options = $attribute->getOptions(); array_shift($options); //remove the first option which is empty - -$product = $objectManager->create(\Magento\Catalog\Model\Product::class) - ->setTypeId('simple') - ->setId(1) - ->setAttributeSetId($attributeSetId) - ->setWebsiteIds([1]) - ->setName('Simple Product 1') - ->setSku('simple1') - ->setPrice(10) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setStockData([ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ]); -$option = array_shift($options); -$product->setTestConfigurable($option->getValue()); -$productRepository->save($product); -$attributeValues[] = [ - 'label' => 'test', - 'attribute_id' => $attribute->getId(), - 'value_index' => $option->getValue(), -]; -$associatedProductIds[] = $product->getId(); -$productAction = $objectManager->get(\Magento\Catalog\Model\Product\Action::class); -$productAction->updateAttributes([$product->getId()], ['test_attribute' => 'test_attribute_value'], $store->getId()); - -$product = $objectManager->create(\Magento\Catalog\Model\Product::class) - ->setTypeId('simple') - ->setId(2) - ->setAttributeSetId($attributeSetId) - ->setWebsiteIds([1]) - ->setName('Simple Product 2') - ->setSku('simple2') - ->setPrice(9.9) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setStockData([ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ]); -$option = array_shift($options); -$product->setTestConfigurable($option->getValue()); -$productRepository->save($product); -$attributeValues[] = [ - 'label' => 'test', - 'attribute_id' => $attribute->getId(), - 'value_index' => $option->getValue(), -]; -$associatedProductIds[] = $product->getId(); +foreach (['simple1', 'simple2'] as $sku) { + $option = array_shift($options); + $product = $productRepository->get($sku); + $product->setTestConfigurable($option->getValue()); + $productRepository->save($product); + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); +} $product = $objectManager->create(\Magento\Catalog\Model\Product::class) ->setTypeId('configurable') @@ -88,7 +47,9 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setStockData([ 'use_config_manage_stock' => 1, - 'is_in_stock' => 0, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, ]); $configurableAttributesData = [ [ diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product_rollback.php new file mode 100644 index 0000000000000..915da48c915b2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/configurable_product_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('configurable', false, null, true); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Nothing to delete +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/simple_products_rollback.php'; +require __DIR__ . '/../../ConfigurableProduct/_files/configurable_attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php new file mode 100644 index 0000000000000..84ce4e1bca87c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/attribute.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$storeManager = $objectManager->get(\Magento\Store\Model\StoreManager::class); +$store = $storeManager->getStore('default'); +$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +$installer = $objectManager->get(\Magento\Catalog\Setup\CategorySetup::class); +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + +$product = $objectManager->create(\Magento\Catalog\Model\Product::class) + ->setTypeId('simple') + ->setId(1) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Simple Product 1') + ->setSku('simple1') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]); +$productRepository->save($product); +$productAction = $objectManager->get(\Magento\Catalog\Model\Product\Action::class); +$productAction->updateAttributes([$product->getId()], ['test_attribute' => 'test_attribute_value'], $store->getId()); + +$product = $objectManager->create(\Magento\Catalog\Model\Product::class) + ->setTypeId('simple') + ->setId(2) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Simple Product 2') + ->setSku('simple2') + ->setPrice(9.9) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php similarity index 87% rename from dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute_rollback.php rename to dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php index 0ce909a3f9ecd..6625b1926fc10 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogRule/_files/product_with_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogRule/_files/simple_products_rollback.php @@ -18,7 +18,7 @@ /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); -foreach (['simple1', 'simple2', 'configurable'] as $sku) { +foreach (['simple1', 'simple2'] as $sku) { try { $product = $productRepository->get($sku, false, null, true); $productRepository->delete($product); @@ -30,4 +30,4 @@ $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); -require __DIR__ . '/../../ConfigurableProduct/_files/configurable_attribute_rollback.php'; +require __DIR__ . '/attribute_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterTest.php deleted file mode 100644 index a8a404a1ee2d6..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterTest.php +++ /dev/null @@ -1,240 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\CatalogSearch\Model\Search\FilterMapper; - -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Search\Adapter\Mysql\ConditionManager; -use Magento\Framework\DB\Select; -use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockRegistryInterface; -use Magento\CatalogInventory\Model\Stock; - -class StockStatusFilterTest extends \PHPUnit\Framework\TestCase -{ - /** - * Const to define if 'show out of stock' configuration flag enabled or not - */ - const SHOW_OUT_OF_STOCK_ENABLED = true; - const SHOW_OUT_OF_STOCK_DISABLED = false; - - /** @var \Magento\Framework\ObjectManagerInterface */ - private $objectManager; - - /** @var ResourceConnection */ - private $resource; - - /** @var ConditionManager */ - private $conditionManager; - - /** @var StockConfigurationInterface */ - private $stockConfiguration; - - /** @var StockRegistryInterface */ - private $stockRegistry; - - /** @var StockStatusFilter */ - private $stockStatusFilter; - - protected function setUp() - { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->resource = $this->objectManager->create(ResourceConnection::class); - $this->conditionManager = $this->objectManager->create(ConditionManager::class); - $this->stockConfiguration = $this->objectManager->create(StockConfigurationInterface::class); - $this->stockRegistry = $this->objectManager->create(StockRegistryInterface::class); - $this->stockStatusFilter = $this->objectManager->create(StockStatusFilter::class); - } - - /** - * @expectedException InvalidArgumentException - * @expectedExceptionMessage Invalid filter type: Luke I am your father! - */ - public function testApplyWithWrongType() - { - $select = $this->resource->getConnection()->select(); - $this->stockStatusFilter->apply( - $select, - Stock::STOCK_IN_STOCK, - 'Luke I am your father!', - self::SHOW_OUT_OF_STOCK_ENABLED - ); - } - - public function testApplyGeneralFilterWithOutOfStock() - { - $select = $this->resource->getConnection()->select(); - $select->from( - ['some_index' => 'some_table'], - ['entity_id' => 'entity_id'] - ); - - $resultSelect = $this->stockStatusFilter->apply( - $select, - Stock::STOCK_IN_STOCK, - StockStatusFilter::FILTER_JUST_ENTITY, - self::SHOW_OUT_OF_STOCK_ENABLED - ); - - $expectedSelect = $this->getExpectedSelectForGeneralFilter(self::SHOW_OUT_OF_STOCK_ENABLED); - - $this->assertEquals( - (string) $expectedSelect, - (string) $resultSelect, - 'Select queries must be the same' - ); - } - - public function testApplyGeneralFilterWithoutOutOfStock() - { - $select = $this->resource->getConnection()->select(); - $select->from( - ['some_index' => 'some_table'], - ['entity_id' => 'entity_id'] - ); - - $resultSelect = $this->stockStatusFilter->apply( - $select, - Stock::STOCK_IN_STOCK, - StockStatusFilter::FILTER_JUST_ENTITY, - self::SHOW_OUT_OF_STOCK_DISABLED - ); - - $expectedSelect = $this->getExpectedSelectForGeneralFilter(self::SHOW_OUT_OF_STOCK_DISABLED); - - $this->assertEquals( - (string) $expectedSelect, - (string) $resultSelect, - 'Select queries must be the same' - ); - } - - public function testApplyFullFilterWithOutOfStock() - { - $select = $this->resource->getConnection()->select(); - $select->from( - ['some_index' => 'some_table'], - ['entity_id' => 'entity_id'] - ); - - $resultSelect = $this->stockStatusFilter->apply( - $select, - Stock::STOCK_IN_STOCK, - StockStatusFilter::FILTER_ENTITY_AND_SUB_PRODUCTS, - self::SHOW_OUT_OF_STOCK_ENABLED - ); - - $expectedSelect = $this->getExpectedSelectForFullFilter(self::SHOW_OUT_OF_STOCK_ENABLED); - - $this->assertEquals( - (string) $expectedSelect, - (string) $resultSelect, - 'Select queries must be the same' - ); - } - - public function testApplyFullFilterWithoutOutOfStock() - { - $select = $this->resource->getConnection()->select(); - $select->from( - ['some_index' => 'some_table'], - ['entity_id' => 'entity_id'] - ); - - $resultSelect = $this->stockStatusFilter->apply( - $select, - Stock::STOCK_IN_STOCK, - StockStatusFilter::FILTER_ENTITY_AND_SUB_PRODUCTS, - self::SHOW_OUT_OF_STOCK_DISABLED - ); - - $expectedSelect = $this->getExpectedSelectForFullFilter(self::SHOW_OUT_OF_STOCK_DISABLED); - - $this->assertEquals( - (string) $expectedSelect, - (string) $resultSelect, - 'Select queries must be the same' - ); - } - - /** - * @param bool $withOutOfStock - * @return Select - */ - private function getExpectedSelectForGeneralFilter($withOutOfStock) - { - $select = $this->resource->getConnection()->select(); - $select->from( - ['some_index' => 'some_table'], - ['entity_id' => 'entity_id'] - )->joinInner( - ['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')], - $this->conditionManager->combineQueries( - [ - 'stock_index.product_id = some_index.entity_id', - $this->conditionManager->generateCondition( - 'stock_index.website_id', - '=', - $this->stockConfiguration->getDefaultScopeId() - ), - $withOutOfStock - ? '' - : $this->conditionManager->generateCondition( - 'stock_index.stock_status', - '=', - Stock::STOCK_IN_STOCK - ), - $this->conditionManager->generateCondition( - 'stock_index.stock_id', - '=', - (int) $this->stockRegistry->getStock()->getStockId() - ), - ], - Select::SQL_AND - ), - [] - ); - - return $select; - } - - /** - * @param bool $withOutOfStock - * @return Select - */ - private function getExpectedSelectForFullFilter($withOutOfStock) - { - $select = $this->getExpectedSelectForGeneralFilter($withOutOfStock); - $select->joinInner( - ['sub_products_stock_index' => $this->resource->getTableName('cataloginventory_stock_status')], - $this->conditionManager->combineQueries( - [ - 'sub_products_stock_index.product_id = some_index.source_id', - $this->conditionManager->generateCondition( - 'sub_products_stock_index.website_id', - '=', - $this->stockConfiguration->getDefaultScopeId() - ), - $withOutOfStock - ? '' - : $this->conditionManager->generateCondition( - 'sub_products_stock_index.stock_status', - '=', - Stock::STOCK_IN_STOCK - ), - $this->conditionManager->generateCondition( - 'sub_products_stock_index.stock_id', - '=', - (int) $this->stockRegistry->getStock()->getStockId() - ), - ], - Select::SQL_AND - ), - [] - ); - - return $select; - } -} diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithFullFilterTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithFullFilterTest.php new file mode 100644 index 0000000000000..e94eda11a3fc5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithFullFilterTest.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ProductRepository; +use Magento\CatalogInventory\Model\Stock; +use Magento\Eav\Model\Config as EavConfig; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Search\Request\Filter\Term; +use Magento\Framework\Search\Request\FilterInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php + * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php + */ +class StockStatusFilterWithFullFilterTest extends TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var StockStatusFilter + */ + private $stockStatusFilter; + + /** + * @var CustomAttributeFilter + */ + private $customAttributeFilter; + + /** + * @var FilterInterface + */ + private $filter; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->resource = $this->objectManager->get(ResourceConnection::class); + $this->stockStatusFilter = $this->objectManager->get(StockStatusFilter::class); + $this->customAttributeFilter = $this->objectManager->get(CustomAttributeFilter::class); + $eavConfig = $this->objectManager->get(EavConfig::class); + $attribute = $eavConfig->getAttribute(Product::ENTITY, 'multiselect_attribute'); + + $productRepository = $this->objectManager->get(ProductRepository::class); + $product = $productRepository->get('simple_ms_2'); + $multiSelectArray = explode(',', $product->getData('multiselect_attribute')); + + $this->filter = $this->objectManager->create( + Term::class, + [ + 'field' => $attribute->getAttributeCode(), + 'name' => $attribute->getAttributeCode() . '_filter', + 'value' => reset($multiSelectArray), + ] + ); + } + + /** + * @param bool $showOutOfStockFlag + * @param int $expectedResult + * @return void + * + * @dataProvider applyDataProvider + */ + public function testApply(bool $showOutOfStockFlag, int $expectedResult) + { + $select = $this->resource->getConnection()->select(); + $select->from( + [$this->resource->getTableName('catalog_product_index_eav')], + ['entity_id'] + )->distinct(true); + $select = $this->customAttributeFilter->apply($select, $this->filter); + $select = $this->stockStatusFilter->apply( + $select, + Stock::STOCK_IN_STOCK, + StockStatusFilter::FILTER_ENTITY_AND_SUB_PRODUCTS, + $showOutOfStockFlag + ); + + $data = $select->query()->fetchAll(); + + $this->assertEquals($expectedResult, count($data)); + } + + /** + * @return array + */ + public function applyDataProvider(): array + { + return [ + [true, 2], + [false, 1], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithGeneralFilterTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithGeneralFilterTest.php new file mode 100644 index 0000000000000..809116e9c7d84 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Search/FilterMapper/StockStatusFilterWithGeneralFilterTest.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\App\ResourceConnection; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * @magentoDbIsolation disabled + * + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php + * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php + */ +class StockStatusFilterWithGeneralFilterTest extends TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var StockStatusFilter + */ + private $stockStatusFilter; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->resource = $this->objectManager->get(ResourceConnection::class); + $this->stockStatusFilter = $this->objectManager->get(StockStatusFilter::class); + } + + /** + * @return void + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid filter type: some_wrong_type + */ + public function testApplyWithWrongType() + { + $select = $this->resource->getConnection()->select(); + $this->stockStatusFilter->apply( + $select, + Stock::STOCK_IN_STOCK, + 'some_wrong_type', + true + ); + } + + /** + * @param bool $showOutOfStockFlag + * @param int $expectedResult + * @return void + * + * @dataProvider applyDataProvider + */ + public function testApply(bool $showOutOfStockFlag, int $expectedResult) + { + $select = $this->resource->getConnection()->select(); + $select->from( + [$this->resource->getTableName('catalog_product_index_eav')], + ['entity_id'] + )->distinct(true); + + $select = $this->stockStatusFilter->apply( + $select, + Stock::STOCK_IN_STOCK, + StockStatusFilter::FILTER_JUST_ENTITY, + $showOutOfStockFlag + ); + $data = $select->query()->fetchAll(); + + $this->assertEquals($expectedResult, count($data)); + } + + /** + * @return array + */ + public function applyDataProvider(): array + { + return [ + [true, 6], + [false, 4], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key.php new file mode 100644 index 0000000000000..cae23dcc9c674 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +require __DIR__ . '/../_files/product_with_category.php'; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$product->setStoreId(1); +$product->setUrlKey('store-1-key'); +$product = $productRepository->save($product); +$linkManagement->assignProductToCategories($product->getSku(), [Category::TREE_ROOT_ID, $category->getEntityId()]); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key_rollback.php new file mode 100644 index 0000000000000..517e9121a6d1e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key_rollback.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +require __DIR__ . '/../_files/product_with_category_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserverTest.php index 34f873df71abb..9de0588356c43 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserverTest.php @@ -11,12 +11,16 @@ /** * @magentoAppArea adminhtml + * @magentoDbIsolation disabled */ class ProductProcessUrlRewriteSavingObserverTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\Framework\ObjectManagerInterface */ protected $objectManager; + /** + * Set up + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandlerTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandlerTest.php new file mode 100644 index 0000000000000..402db06a0bbc9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandlerTest.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Observer; + +use Magento\Catalog\Api\CategoryListInterface; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use PHPUnit\Framework\TestCase; + +/** + * @magentoAppArea adminhtml + */ +class UrlRewriteHandlerTest extends TestCase +{ + /** + * @var UrlRewriteHandler + */ + private $handler; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->handler = $this->objectManager->get(UrlRewriteHandler::class); + } + + /** + * Checks category URLs rewrites generation with enabled `Use Categories Path for Product URLs` option and + * store's specific product URL key. + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/CatalogUrlRewrite/Fixtures/product_custom_url_key.php + * @magentoConfigFixture admin_store catalog/seo/product_use_categories 1 + */ + public function testGenerateProductUrlRewrites() + { + $product = $this->getProduct('p002'); + $category = $this->getCategory('category 1'); + // change the category scope to the global + $category->setStoreId(0) + ->setChangedProductIds([$product->getId()]) + ->setAffectedProductIds([$product->getId()]) + ->setAnchorsAbove(false); + + $generatedUrls = $this->handler->generateProductUrlRewrites($category); + $actual = array_values(array_map(function (UrlRewrite $urlRewrite) { + return $urlRewrite->getRequestPath(); + }, $generatedUrls)); + + $expected = [ + 'store-1-key.html', // the Default store + 'cat-1/store-1-key.html', // the Default store with Category URL key + '/store-1-key.html', // an anchor URL the Default store + 'p002.html', // the Secondary store + 'cat-1-2/p002.html', // the Secondary store with Category URL key + '/p002.html', // an anchor URL the Secondary store + ]; + self::assertEquals($expected, $actual, 'Generated URLs rewrites do not match.'); + } + + /** + * Gets category by name. + * + * @param string $name + * @return CategoryInterface + */ + private function getCategory(string $name): CategoryInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) + ->create(); + /** @var CategoryListInterface $repository */ + $repository = $this->objectManager->get(CategoryListInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } + + /** + * Gets product by SKU. + * + * @param string $sku + * @return ProductInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getProduct(string $sku): ProductInterface + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + return $productRepository->get($sku); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Store/Block/SwitcherTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Store/Block/SwitcherTest.php deleted file mode 100644 index 5b3879b592245..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Plugin/Store/Block/SwitcherTest.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CatalogUrlRewrite\Plugin\Store\Block; - -/** - * Integration tests for Magento\CatalogUrlRewrite\Plugin\Store\Block\Switcher block. - */ -class SwitcherTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\TestFramework\ObjectManager - */ - private $objectManager; - - /** - * @var \Magento\Store\Block\Switcher - */ - private $model; - - /** - * @var \Magento\Store\Api\StoreRepositoryInterface - */ - private $storeRepository; - - /** - * {@inheritdoc} - */ - protected function setUp() - { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->model = $this->objectManager->create(\Magento\Store\Block\Switcher::class); - $this->storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); - } - - /** - * Test that after switching from Store 1 to Store 2 with another root Category user gets correct store url. - * - * @magentoDataFixture Magento/Store/_files/store.php - * @magentoDataFixture Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups.php - * @magentoAppArea frontend - * @return void - */ - public function testGetTargetStorePostData(): void - { - $storeCode = 'test'; - $store = $this->storeRepository->get($storeCode); - $result = json_decode($this->model->getTargetStorePostData($store), true); - - $this->assertContains($storeCode, $result['action']); - } -} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_rewrite_multistore_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_rewrite_multistore_rollback.php index 1ac9fbf63b118..bcf399cb5e552 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_rewrite_multistore_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_rewrite_multistore_rollback.php @@ -4,9 +4,30 @@ * See COPYING.txt for license details. */ -use Magento\TestFramework\Helper\Bootstrap; +declare(strict_types=1); -$objectManager = Bootstrap::getObjectManager(); +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('product1', true); + if ($product->getId()) { + $productRepository->delete($product); + } +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); require __DIR__ . '/../../Store/_files/store_rollback.php'; require __DIR__ . '/../../Store/_files/second_store_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_category.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_category.php index aa753a3509987..2f3d4ea4c3e7f 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_category.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_category.php @@ -75,7 +75,7 @@ /** @var ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->get(ProductRepositoryInterface::class); -$productRepository->save($product); +$product = $productRepository->save($product); /** @var CategoryLinkManagementInterface $linkManagement */ $linkManagement = $objectManager->get(CategoryLinkManagementInterface::class); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups.php deleted file mode 100644 index f77413e18f472..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups.php +++ /dev/null @@ -1,67 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); -$defaultCategory = $objectManager->create(\Magento\Catalog\Helper\DefaultCategory::class); -/** @var \Magento\Catalog\Model\Category $category */ -$category = $objectManager->create(\Magento\Catalog\Model\Category::class); -$category->isObjectNew(true); -$category->setCreatedAt('2014-06-23 09:50:07') - ->setName('Category 1') - ->setParentId($defaultCategory->getId()) - ->setPath('1/2/3') - ->setLevel(2) - ->setAvailableSortBy('name') - ->setDefaultSortBy('name') - ->setIsActive(true) - ->setPosition(1) - ->setAvailableSortBy(['position']) - ->save(); - -/** @var \Magento\Store\Model\Store $store */ -$store = $objectManager->create(\Magento\Store\Model\Store::class); - -$category->setStoreId($store->load('default')->getId()) - ->setName('category-default-store') - ->setUrlKey('category-default-store') - ->save(); - -$rootCategoryForTestStoreGroup = $objectManager->create(\Magento\Catalog\Model\Category::class); -$rootCategoryForTestStoreGroup->isObjectNew(true); -$rootCategoryForTestStoreGroup->setCreatedAt('2014-06-23 09:50:07') - ->setName('Category 2') - ->setParentId(\Magento\Catalog\Model\Category::TREE_ROOT_ID) - ->setPath('1/2/334') - ->setLevel(2) - ->setAvailableSortBy('name') - ->setDefaultSortBy('name') - ->setIsActive(true) - ->setPosition(1) - ->setAvailableSortBy(['position']) - ->save(); - -$rootCategoryForTestStoreGroup->setStoreId($store->load('test')->getId()) - ->setName('category-test-store') - ->setUrlKey('category-test-store') - ->save(); - -$storeCode = 'test'; -/** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ -$storeRepository = $objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); -/** @var \Magento\Store\Api\Data\StoreInterface $store */ -$store = $storeRepository->get($storeCode); - -/** @var \Magento\Store\Model\Group $storeGroup */ -$storeGroup = $objectManager->create(\Magento\Store\Model\Group::class) - ->setWebsiteId('1') - ->setCode('test_store_group') - ->setName('Test Store Group') - ->setRootCategoryId($rootCategoryForTestStoreGroup->getId()) - ->setDefaultStoreId($store->getId()) - ->save(); - -$store->setGroupId($storeGroup->getId())->save(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups_rollback.php deleted file mode 100644 index 9592e9d0e69b4..0000000000000 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/two_categories_per_two_store_groups_rollback.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -use Magento\Framework\Registry; -use Magento\Store\Model\Group; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\Catalog\Api\CategoryListInterface; -use Magento\Catalog\Api\CategoryRepositoryInterface; -use Magento\Framework\Api\SearchCriteriaBuilder; - -$objectManager = Bootstrap::getObjectManager(); - -/** @var Registry $registry */ -$registry = $objectManager->get(Registry::class); -$registry->unregister('isSecureArea'); -$registry->register('isSecureArea', true); -// Delete first category -/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ -$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); -$searchCriteria = $searchCriteriaBuilder->addFilter('name', 'Category 1')->create(); -/** @var CategoryListInterface $categoryList */ -$categoryList = $objectManager->get(CategoryListInterface::class); -$categories = $categoryList->getList($searchCriteria)->getItems(); -/** @var CategoryRepositoryInterface $categoryRepository */ -$categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); -foreach ($categories as $category) { - $categoryRepository->delete($category); -} -// Delete second category -$searchCriteria = $searchCriteriaBuilder->addFilter('name', 'Category 2')->create(); -$categories = $categoryList->getList($searchCriteria)->getItems(); -foreach ($categories as $category) { - $categoryRepository->delete($category); -} -// Delete store group -/** @var Group $store */ -$storeGroup = $objectManager->get(Group::class); -$storeGroup->load('test_store_group', 'code'); -if ($storeGroup->getId()) { - $storeGroup->delete(); -} -$registry->unregister('isSecureArea'); -$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php index 61ce3315fd9b5..f7967eee8b247 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Model/Rule/Condition/ProductTest.php @@ -6,6 +6,8 @@ namespace Magento\CatalogWidget\Model\Rule\Condition; +use Magento\Catalog\Api\Data\ProductInterface; + class ProductTest extends \PHPUnit\Framework\TestCase { /** @@ -18,6 +20,9 @@ class ProductTest extends \PHPUnit\Framework\TestCase */ protected $objectManager; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -28,19 +33,26 @@ protected function setUp() $this->conditionProduct->setRule($rule); } + /** + * @return void + */ public function testLoadAttributeOptions() { $this->conditionProduct->loadAttributeOptions(); $options = $this->conditionProduct->getAttributeOption(); - $this->assertArrayHasKey('sku', $options); - $this->assertArrayHasKey('attribute_set_id', $options); + $this->assertArrayHasKey(ProductInterface::SKU, $options); + $this->assertArrayHasKey(ProductInterface::ATTRIBUTE_SET_ID, $options); $this->assertArrayHasKey('category_ids', $options); + $this->assertArrayNotHasKey(ProductInterface::STATUS, $options); foreach ($options as $code => $label) { $this->assertNotEmpty($label); $this->assertNotEmpty($code); } } + /** + * @return void + */ public function testAddGlobalAttributeToCollection() { $collection = $this->objectManager->create(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); @@ -53,6 +65,9 @@ public function testAddGlobalAttributeToCollection() $this->assertEquals('at_special_price.value', $this->conditionProduct->getMappedSqlField()); } + /** + * @return void + */ public function testAddNonGlobalAttributeToCollectionNoProducts() { $collection = $this->objectManager->create(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php new file mode 100644 index 0000000000000..4c653ab9ae33f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Controller\Cart; + +use Magento\Catalog\Model\Product; +use Magento\Checkout\Model\Session; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Serialize\Serializer\Json; + +class UpdateItemQtyTest extends \Magento\TestFramework\TestCase\AbstractController +{ + /** + * @var Json + */ + private $json; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @var Session + */ + private $session; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + protected function setUp() + { + parent::setUp(); + + $this->json = $this->_objectManager->create(Json::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + $this->session = $this->_objectManager->create(Session::class); + $this->productRepository = $this->_objectManager->create(ProductRepositoryInterface::class); + } + + /** + * Tests of cart validation. + * + * @param array $requestQuantity + * @param array $expectedResponse + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/Checkout/_files/quote_with_simple_product.php + * @dataProvider requestDataProvider + */ + public function testExecute($requestQuantity, $expectedResponse) + { + try { + /** @var $product Product */ + $product = $this->productRepository->get('simple'); + } catch (\Exception $e) { + $this->fail('No such product entity'); + } + + $quoteItem = $this->session + ->getQuote() + ->getItemByProduct($product); + + $this->assertNotNull($quoteItem, 'Cannot get quote item for simple product'); + + $request = []; + if (!empty($requestQuantity) && is_array($requestQuantity)) { + $request= [ + 'form_key' => $this->formKey->getFormKey(), + 'cart' => [ + $quoteItem->getId() => $requestQuantity, + ] + ]; + } + + $this->getRequest()->setPostValue($request); + $this->dispatch('checkout/cart/updateItemQty'); + $response = $this->getResponse()->getBody(); + + $this->assertEquals($this->json->unserialize($response), $expectedResponse); + } + + /** + * Variations of request data. + * @returns array + */ + public function requestDataProvider(): array + { + return [ + [ + 'request' => [], + 'response' => [ + 'success' => false, + 'error_message' => 'Something went wrong while saving the page.'. + ' Please refresh the page and try again.' + ] + ], + [ + 'request' => ['qty' => 2], + 'response' => [ + 'success' => true, + ] + ], + [ + 'request' => ['qty' => 230], + 'response' => [ + 'success' => false, + 'error_message' => 'The requested qty is not available'] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php index 5ffaa789cf2eb..52156040b2800 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php @@ -108,6 +108,7 @@ public function testConfigureActionWithSimpleProductAndCustomOption() * Test for \Magento\Checkout\Controller\Cart::configureAction() with bundle product * * @magentoDataFixture Magento/Checkout/_files/quote_with_bundle_product.php + * @magentoDbIsolation disabled */ public function testConfigureActionWithBundleProduct() { @@ -238,7 +239,7 @@ public function testUpdatePostAction() * Gets \Magento\Quote\Model\Quote\Item from \Magento\Quote\Model\Quote by product id * * @param \Magento\Quote\Model\Quote $quote - * @param $productId + * @param int $productId * @return \Magento\Quote\Model\Quote\Item|null */ private function _getQuoteItemIdByProductId($quote, $productId) diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php new file mode 100644 index 0000000000000..e533dbfa0c879 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Plugin\Model\Quote; + +use Magento\Checkout\Model\Cart; +use Magento\Checkout\Model\Session; +use Magento\Quote\Model\BillingAddressManagement; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address as QuoteAddress; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Test for \Magento\Checkout\Plugin\Model\Quote\ResetQuoteAddresses + */ +class ResetQuoteAddressesTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php + * + * @return void + */ + public function testAfterRemoveItem(): void + { + /** @var Quote $quote */ + $quote = Bootstrap::getObjectManager()->create(Quote::class); + $quote->load('test_order_with_virtual_product', 'reserved_order_id'); + /** @var QuoteAddress $quoteShippingAddress */ + $quoteBillingAddress = Bootstrap::getObjectManager()->create(QuoteAddress::class); + $quoteBillingAddress->setRegion('CA') + ->setPostcode('90210') + ->setFirstname('a_unique_firstname') + ->setLastname('lastname') + ->setStreet('street') + ->setCity('Beverly Hills') + ->setEmail('admin@example.com') + ->setTelephone('1111111111') + ->setCountryId('US') + ->setAddressType('billing'); + + /** @var BillingAddressManagement $billingAddressManagement */ + $billingAddressManagement = Bootstrap::getObjectManager()->create(BillingAddressManagement::class); + $billingAddressManagement->assign($quote->getId(), $quoteBillingAddress); + /** @var Session $checkoutSession */ + $checkoutSession = Bootstrap::getObjectManager()->create(Session::class); + $checkoutSession->setQuoteId($quote->getId()); + /** @var Cart $cart */ + $cart = Bootstrap::getObjectManager()->create(Cart::class); + + $activeQuote = $cart->getQuote(); + $cart->removeItem($activeQuote->getAllVisibleItems()[0]->getId()); + $cart->save(); + + /** @var Quote $quote */ + $quote = Bootstrap::getObjectManager()->create(Quote::class); + $quote->load('test_order_with_virtual_product', 'reserved_order_id'); + $quoteBillingAddressUpdated = $quote->getBillingAddress(); + $customer = $quote->getCustomer(); + + $this->assertEquals($quoteBillingAddressUpdated->getEmail(), $customer->getEmail()); + $this->assertEmpty($quoteBillingAddressUpdated->getCountryId()); + $this->assertEmpty($quoteBillingAddressUpdated->getRegionId()); + $this->assertEmpty($quoteBillingAddressUpdated->getRegion()); + $this->assertEmpty($quoteBillingAddressUpdated->getPostcode()); + $this->assertEmpty($quoteBillingAddressUpdated->getCity()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product.php index ddc7d20631566..c88c87d74dfda 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product.php @@ -35,6 +35,7 @@ /** @var $cart \Magento\Checkout\Model\Cart */ $cart = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Checkout\Model\Cart::class); $cart->addProduct($product, $requestInfo); +$cart->getQuote()->setReservedOrderId('test_cart_with_bundle'); $cart->save(); /** @var $objectManager \Magento\TestFramework\ObjectManager */ diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product_rollback.php new file mode 100644 index 0000000000000..090248820e246 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_bundle_product_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/* + * Since the bundle product creation GUI doesn't allow to choose values for bundled products' custom options, + * bundled items should not contain products with required custom options. + * However, if to create such a bundle product, it will be always out of stock. + */ +require __DIR__ . '/../../../Magento/Catalog/_files/products_rollback.php'; + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('bundle-product', false, null, true); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed +} + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->load('test_cart_with_bundle', 'reserved_order_id'); +$quote->delete(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/FulltextGridSearchTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/FulltextGridSearchTest.php new file mode 100644 index 0000000000000..c740609773b90 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/FulltextGridSearchTest.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Controller\Adminhtml; + +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * @magentoDataFixture Magento/Cms/Fixtures/page_list.php + */ +class FulltextGridSearchTest extends AbstractBackendController +{ + /** + * Checks a fulltext grid search by CMS page title. + * + * @param string $query + * @param int $expectedRows + * @param array $expectedTitles + * @dataProvider queryDataProvider + */ + public function testSearchByTitle(string $query, int $expectedRows, array $expectedTitles) + { + $url = 'backend/mui/index/render/?namespace=cms_page_listing&search=' . $query; + + $this->getRequest() + ->getHeaders() + ->addHeaderLine('Accept', 'application/json'); + $this->dispatch($url); + $response = $this->getResponse(); + $data = json_decode($response->getBody(), true); + self::assertEquals($expectedRows, $data['totalRecords']); + + $titleList = array_column($data['items'], 'title'); + self::assertEquals($expectedTitles, $titleList); + } + + /** + * Gets list of variations with different search queries. + * + * @return array + */ + public function queryDataProvider(): array + { + return [ + [ + 'query' => 'simple', + 'expectedRows' => 3, + 'expectedTitles' => ['simplePage', 'simplePage01', '01simplePage'] + ], + [ + 'query' => 'page01', + 'expectedRows' => 1, + 'expectedTitles' => ['simplePage01'] + ], + [ + 'query' => '01simple', + 'expectedRows' => 1, + 'expectedTitles' => ['01simplePage'] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php index bbcbc40dc6640..fa49a7b1f57e1 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFilesTest.php @@ -101,11 +101,7 @@ public function testExecuteWithWrongFileName() $this->model->getStorage()->getSession()->setCurrentPath($this->fullDirectoryPath); $this->model->execute(); - $this->assertTrue( - $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . $fileName) - ) - ); + $this->assertFileExists($this->fullDirectoryPath . $fileName); } /** diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolderTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolderTest.php index 8e30e85541a42..a1a29706756b5 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolderTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolderTest.php @@ -112,11 +112,7 @@ public function testExecuteWithWrongDirectoryName() $this->model->getRequest()->setParams(['node' => $this->imagesHelper->idEncode($directoryName)]); $this->model->execute(); - $this->assertTrue( - $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . $directoryName) - ) - ); + $this->assertFileExists($this->fullDirectoryPath . $directoryName); } /** diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolderTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolderTest.php index 0c74f18e9c44a..e509737a0020f 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolderTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolderTest.php @@ -111,10 +111,8 @@ public function testExecuteWithWrongPath() $this->model->getStorage()->getSession()->setCurrentPath($this->fullDirectoryPath . $dirPath); $this->model->execute(); - $this->assertFalse( - $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . $dirPath . $this->dirName) - ) + $this->assertFileNotExists( + $this->fullDirectoryPath . $dirPath . $this->dirName ); } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php index 534eb3db35b3f..bab14a8663eae 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php @@ -125,10 +125,8 @@ public function testExecuteWithWrongPath() $this->model->getStorage()->getSession()->setCurrentPath($dirPath); $this->model->execute(); - $this->assertFalse( - $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . $dirPath . $this->fileName) - ) + $this->assertFileNotExists( + $this->fullDirectoryPath . $dirPath . $this->fileName ); } @@ -147,11 +145,7 @@ public function testExecuteWithWrongFileName() $this->model->getStorage()->getSession()->setCurrentPath($this->fullDirectoryPath); $this->model->execute(); - $this->assertFalse( - $this->mediaDirectory->isExist( - $this->mediaDirectory->getRelativePath($this->fullDirectoryPath . $newFilename) - ) - ); + $this->assertFileNotExists($this->fullDirectoryPath . $newFilename); } /** diff --git a/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list.php b/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list.php new file mode 100644 index 0000000000000..ae431f5c4cf1a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$data = [ + [ + 'title' => 'simplePage', + 'is_active' => 1 + ], + [ + 'title' => 'simplePage01', + 'is_active' => 1 + ], + [ + 'title' => '01simplePage', + 'is_active' => 1 + ], +]; + +/** @var PageRepositoryInterface $pageRepository */ +$pageRepository = $objectManager->get(PageRepositoryInterface::class); +foreach ($data as $item) { + $page = $objectManager->create(PageInterface::class, ['data' => $item]); + $pageRepository->save($page); +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list_rollback.php b/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list_rollback.php new file mode 100644 index 0000000000000..261cdba589653 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Fixtures/page_list_rollback.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var PageRepositoryInterface $pageRepository */ +$pageRepository = $objectManager->get(PageRepositoryInterface::class); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('title', ['simplePage', 'simplePage01', '01simplePage'], 'in') + ->create(); +$result = $pageRepository->getList($searchCriteria); + +foreach ($result->getItems() as $item) { + $pageRepository->delete($item); +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/ConfigTest.php index b79ccd255f26a..983d5657a4cee 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/ConfigTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/ConfigTest.php @@ -18,6 +18,9 @@ class ConfigTest extends \PHPUnit\Framework\TestCase */ private $model; + /** + * {@inheritdoc} + */ protected function setUp() { $objectManager = Bootstrap::getObjectManager(); @@ -26,6 +29,8 @@ protected function setUp() /** * Tests that config returns valid config array in it + * + * @return void */ public function testGetConfig() { @@ -35,6 +40,8 @@ public function testGetConfig() /** * Tests that config returns right urls going to the published library path + * + * @return void */ public function testGetConfigCssUrls() { @@ -53,11 +60,12 @@ public function testGetConfigCssUrls() /** * Test enabled module is able to modify WYSIWYG config + * * @return void * - * @magentoConfigFixture default/cms/wysiwyg/editor testAdapter + * @magentoConfigFixture default/cms/wysiwyg/editor Magento_TestModuleWysiwygConfig/wysiwyg/tinymce4TestAdapter */ - public function testEnabledModuleIsAbleToModifyConfig() + public function testTestModuleEnabledModuleIsAbleToModifyConfig() { $objectManager = Bootstrap::getObjectManager(); $compositeConfigProvider = $objectManager->create(\Magento\Cms\Model\Wysiwyg\CompositeConfigProvider::class); @@ -68,5 +76,12 @@ public function testEnabledModuleIsAbleToModifyConfig() $config = $model->getConfig(); $this->assertEquals(TestModuleWysiwygConfig::CONFIG_HEIGHT, $config['height']); $this->assertEquals(TestModuleWysiwygConfig::CONFIG_CONTENT_CSS, $config['content_css']); + $this->assertArrayHasKey('tinymce4', $config); + $this->assertArrayHasKey('toolbar', $config['tinymce4']); + $this->assertNotContains( + 'charmap', + $config['tinymce4']['toolbar'], + 'Failed to address that the custom test module removes "charmap" button from the toolbar' + ); } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Setup/ContentConverterTest.php b/dev/tests/integration/testsuite/Magento/Cms/Setup/ContentConverterTest.php index b3bbd64b83e7e..7dbc3d2cc1dbd 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Setup/ContentConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Setup/ContentConverterTest.php @@ -34,8 +34,8 @@ public function convertDataProvider() <h2 class="title">Hot Sellers</h2> <p class="info">Here is what`s trending on Luma right now</p> </div>'; - $serializedWidgetContent = '{{widget type="Magento\\CatalogWidget\\Block\\Product\\ProductsList" products_per_page="8" products_count="8" template="product/widget/content/grid.phtml" conditions_encoded="a:2:[i:1;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Combine`;s:10:`aggregator`;s:3:`all`;s:5:`value`;s:1:`1`;s:9:`new_child`;s:0:``;]s:4:`1--1`;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Product`;s:9:`attribute`;s:3:`sku`;s:8:`operator`;s:2:`()`;s:5:`value`;a:8:[i:0;s:4:`WS12`;i:1;s:4:`WT09`;i:2;s:4:`MT07`;i:3;s:4:`MH07`;i:4;s:7:`24-MB02`;i:5;s:7:`24-WB04`;i:6;s:8:`241-MB08`;i:7;s:8:`240-LV05`;]]]"}}'; - $jsonEncodedWidgetContent = '{{widget type="Magento\\CatalogWidget\\Block\\Product\\ProductsList" products_per_page="8" products_count="8" template="product/widget/content/grid.phtml" conditions_encoded="^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,`aggregator`:`all`,`value`:`1`,`new_child`:``^],`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,`attribute`:`sku`,`operator`:`()`,`value`:[`WS12`,`WT09`,`MT07`,`MH07`,`24-MB02`,`24-WB04`,`241-MB08`,`240-LV05`]^]^]"}}'; + $serializedWidgetContent = '{{widget type="Magento\\CatalogWidget\\Block\\Product\\ProductsList" products_per_page="8" products_count="8" template="Magento_CatalogWidget::product/widget/content/grid.phtml" conditions_encoded="a:2:[i:1;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Combine`;s:10:`aggregator`;s:3:`all`;s:5:`value`;s:1:`1`;s:9:`new_child`;s:0:``;]s:4:`1--1`;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Product`;s:9:`attribute`;s:3:`sku`;s:8:`operator`;s:2:`()`;s:5:`value`;a:8:[i:0;s:4:`WS12`;i:1;s:4:`WT09`;i:2;s:4:`MT07`;i:3;s:4:`MH07`;i:4;s:7:`24-MB02`;i:5;s:7:`24-WB04`;i:6;s:8:`241-MB08`;i:7;s:8:`240-LV05`;]]]"}}'; + $jsonEncodedWidgetContent = '{{widget type="Magento\\CatalogWidget\\Block\\Product\\ProductsList" products_per_page="8" products_count="8" template="Magento_CatalogWidget::product/widget/content/grid.phtml" conditions_encoded="^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,`aggregator`:`all`,`value`:`1`,`new_child`:``^],`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,`attribute`:`sku`,`operator`:`()`,`value`:[`WS12`,`WT09`,`MT07`,`MH07`,`24-MB02`,`24-WB04`,`241-MB08`,`240-LV05`]^]^]"}}'; // @codingStandardsIgnoreEnd return [ 'no widget' => [ diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/SpecialPriceIndexerWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/SpecialPriceIndexerWithDimensionTest.php new file mode 100644 index 0000000000000..578cb47d14676 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/SpecialPriceIndexerWithDimensionTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Pricing\Price; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoDbIsolation disabled + * @group indexer_dimension + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + */ +class SpecialPriceIndexerWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var CollectionFactory + */ + private $productCollectionFactory; + + /** + * @var Processor + */ + private $indexerProcessor; + + /** + * Set up + */ + protected function setUp() + { + $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $this->productCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); + $this->indexerProcessor = Bootstrap::getObjectManager()->get(Processor::class); + } + + /** + * Use collection to check data in index + * Do not use magentoDbIsolation because index statement changing "tears" transaction (triggers creating) + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoDataFixture Magento/Catalog/_files/enable_price_index_schedule.php + */ + public function testFullReindexIfChildHasSpecialPrice() + { + $specialPrice = 2; + /** @var Product $childProduct */ + $childProduct = $this->productRepository->get('simple_10', true); + $childProduct->setData('special_price', $specialPrice); + $this->productRepository->save($childProduct); + + /** @var ProductCollection $collection */ + $collection = $this->productCollectionFactory->create(); + $collection + ->addPriceData() + ->addFieldToFilter(ProductInterface::SKU, 'configurable'); + + /** @var Product[] $items */ + $items = array_values($collection->getItems()); + self::assertEquals(10, $items[0]->getData('min_price')); + + $this->indexerProcessor->reindexAll(); + + /** @var ProductCollection $collection */ + $collection = $this->productCollectionFactory->create(); + $collection + ->addPriceData() + ->addFieldToFilter(ProductInterface::SKU, 'configurable'); + + /** @var Product $item */ + $item = $collection->getFirstItem(); + self::assertEquals($specialPrice, $item->getData('min_price')); + } + + /** + * Use collection to check data in index + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoDbIsolation disabled + */ + public function testOnSaveIndexationIfChildHasSpecialPrice() + { + $specialPrice = 2; + /** @var Product $childProduct */ + $childProduct = $this->productRepository->get('simple_10', true); + $childProduct->setData('special_price', $specialPrice); + $this->productRepository->save($childProduct); + + /** @var ProductCollection $collection */ + $collection = $this->productCollectionFactory->create(); + $collection + ->addPriceData() + ->addFieldToFilter(ProductInterface::SKU, 'configurable'); + + /** @var Product $item */ + $item = $collection->getFirstItem(); + self::assertEquals($specialPrice, $item->getData('min_price')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox/RenderingBasedOnIsProductListFlagWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox/RenderingBasedOnIsProductListFlagWithDimensionTest.php new file mode 100644 index 0000000000000..b2e4fe6af3243 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox/RenderingBasedOnIsProductListFlagWithDimensionTest.php @@ -0,0 +1,154 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Pricing\Price\FinalPrice; +use Magento\Catalog\Pricing\Render\FinalPriceBox; +use Magento\Framework\Pricing\Render\Amount; +use Magento\Framework\Pricing\Render\RendererPool; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoDbIsolation disabled + * @magentoIndexerDimensionMode catalog_product_price website_and_customer_group + * @group indexer_dimension + * Test price rendering according to is_product_list flag for Configurable product + */ +class RenderingBasedOnIsProductListFlagWithDimensionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ProductInterface + */ + private $product; + + /** + * @var FinalPrice + */ + private $finalPrice; + + /** + * @var RendererPool + */ + private $rendererPool; + + /** + * @var FinalPriceBox + */ + private $finalPriceBox; + + /** + * Set up + */ + protected function setUp() + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $this->product = $productRepository->get('configurable'); + $this->finalPrice = Bootstrap::getObjectManager()->create(FinalPrice::class, [ + 'saleableItem' => $this->product, + 'quantity' => null + ]); + $this->rendererPool = Bootstrap::getObjectManager()->create(RendererPool::class); + $this->rendererPool->setData( + [ + 'default' => + [ + 'default_amount_render_class' => Amount::class, + 'default_amount_render_template' => 'Magento_Catalog::product/price/amount/default.phtml', + ], + ] + ); + $this->finalPriceBox = Bootstrap::getObjectManager()->create(FinalPriceBox::class, [ + 'saleableItem' => $this->product, + 'price' => $this->finalPrice, + 'rendererPool' => $this->rendererPool, + ]); + $this->finalPriceBox->setTemplate('Magento_ConfigurableProduct::product/price/final_price.phtml'); + + /** @var Product $childProduct */ + $childProduct = $productRepository->get('simple_10', true); + $childProduct->setData('special_price', 5.99); + $productRepository->save($childProduct); + } + + /** + * Test when is_product_list flag is not specified. Regular and Special price should be rendered + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea frontend + * @magentoDbIsolation disabled + */ + public function testRenderingByDefault() + { + $html = $this->finalPriceBox->toHtml(); + self::assertContains('5.99', $html); + $this->assertGreaterThanOrEqual( + 1, + \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( + '//*[contains(@class,"normal-price")]', + $html + ) + ); + $this->assertGreaterThanOrEqual( + 1, + \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( + '//*[contains(@class,"old-price")]', + $html + ) + ); + } + + /** + * Test when is_product_list flag is specified + * + * Special price should be valid + * FinalPriceBox::hasSpecialPrice should not be call + * Regular price for Configurable product should be rendered for is_product_list = false (product page), but not be + * for for is_product_list = true (list of products) + * + * @param bool $flag + * @param int|bool $count + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea frontend + * @dataProvider isProductListDataProvider + * @magentoDbIsolation disabled + */ + public function testRenderingAccordingToIsProductListFlag($flag, $count) + { + $this->finalPriceBox->setData('is_product_list', $flag); + $html = $this->finalPriceBox->toHtml(); + self::assertContains('5.99', $html); + $this->assertEquals( + 1, + \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( + '//*[contains(@class,"normal-price")]', + $html + ) + ); + $this->assertEquals( + $count, + \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( + '//*[contains(@class,"old-price")]', + $html + ) + ); + } + + /** + * @return array + */ + public function isProductListDataProvider() + { + return [ + 'is_not_product_list' => [false, 1], + 'is_product_list' => [true, 0], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php index a06a981a4c891..2f89cb72b0cd9 100644 --- a/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Contact/Controller/IndexTest.php @@ -6,11 +6,16 @@ namespace Magento\Contact\Controller; +use Zend\Http\Request; + /** * Contact index controller test */ class IndexTest extends \Magento\TestFramework\TestCase\AbstractController { + /** + * testPostAction + */ public function testPostAction() { $params = [ @@ -20,6 +25,7 @@ public function testPostAction() 'hideit' => '', ]; $this->getRequest()->setPostValue($params); + $this->getRequest()->setMethod(Request::METHOD_POST); $this->dispatch('contact/index/post'); $this->assertRedirect($this->stringContains('contact/index')); @@ -33,12 +39,13 @@ public function testPostAction() /** * @dataProvider dataInvalidPostAction - * @param $params - * @param $expectedMessage + * @param array $params + * @param string $expectedMessage */ public function testInvalidPostAction($params, $expectedMessage) { $this->getRequest()->setPostValue($params); + $this->getRequest()->setMethod(Request::METHOD_POST); $this->dispatch('contact/index/post'); $this->assertRedirect($this->stringContains('contact/index')); @@ -48,6 +55,9 @@ public function testInvalidPostAction($params, $expectedMessage) ); } + /** + * @return array + */ public static function dataInvalidPostAction() { return [ diff --git a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRatesTest.php b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRatesTest.php index ce83b7d3a6e06..8e25e5960a4b5 100644 --- a/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRatesTest.php +++ b/dev/tests/integration/testsuite/Magento/CurrencySymbol/Controller/Adminhtml/System/Currency/FetchRatesTest.php @@ -6,12 +6,17 @@ namespace Magento\CurrencySymbol\Controller\Adminhtml\System\Currency; +/** + * Fetch Rates Test + */ class FetchRatesTest extends \Magento\TestFramework\TestCase\AbstractBackendController { /** * Test fetch action without service + * + * @return void */ - public function testFetchRatesActionWithoutService() + public function testFetchRatesActionWithoutService(): void { $request = $this->getRequest(); $request->setParam( @@ -28,8 +33,10 @@ public function testFetchRatesActionWithoutService() /** * Test save action with nonexistent service + * + * @return void */ - public function testFetchRatesActionWithNonexistentService() + public function testFetchRatesActionWithNonexistentService(): void { $request = $this->getRequest(); $request->setParam( @@ -43,85 +50,4 @@ public function testFetchRatesActionWithNonexistentService() \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); } - - /** - * Test save action with nonexistent service - */ - public function testFetchRatesActionWithServiceErrors() - { - $this->runActionWithMockedImportService(['We can\'t retrieve a rate from url']); - - $this->assertSessionMessages( - $this->contains('Click "Save" to apply the rates we found.'), - \Magento\Framework\Message\MessageInterface::TYPE_WARNING - ); - } - - /** - * Test save action with nonexistent service - */ - public function testFetchRatesActionWithoutServiceErrors() - { - $this->runActionWithMockedImportService(); - - $this->assertSessionMessages( - $this->contains('Click "Save" to apply the rates we found.'), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } - - /** - * Run fetchRates with mocked external import service - * - * @param array $messages messages from external import service - */ - protected function runActionWithMockedImportService(array $messages = []) - { - $importServiceMock = $this->getMockBuilder(\Magento\Directory\Model\Currency\Import\Webservicex::class) - ->disableOriginalConstructor() - ->getMock(); - - $importServiceMock->method('fetchRates') - ->willReturn(['USD' => ['USD' => 1]]); - - $importServiceMock->method('getMessages') - ->willReturn($messages); - - $backendSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Session::class) - ->disableOriginalConstructor() - ->getMock(); - - $importServiceFactoryMock = $this->getMockBuilder(\Magento\Directory\Model\Currency\Import\Factory::class) - ->disableOriginalConstructor() - ->getMock(); - - $importServiceFactoryMock->method('create') - ->willReturn($importServiceMock); - - $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $objectManagerMap = [ - [\Magento\Directory\Model\Currency\Import\Factory::class, $importServiceFactoryMock], - [\Magento\Backend\Model\Session::class, $backendSessionMock] - ]; - - $objectManagerMock->method('get') - ->will($this->returnValueMap($objectManagerMap)); - - $context = $this->_objectManager->create( - \Magento\Backend\App\Action\Context::class, - ["objectManager" => $objectManagerMock] - ); - $registry = $this->_objectManager->get(\Magento\Framework\Registry::class); - - $this->getRequest()->setParam('rate_services', 'webservicex'); - - $action = new \Magento\CurrencySymbol\Controller\Adminhtml\System\Currency\FetchRates( - $context, - $registry - ); - $action->execute(); - } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Form/RegisterTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Form/RegisterTest.php index 874811d159050..cb07b1a401fdf 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Form/RegisterTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Form/RegisterTest.php @@ -5,6 +5,10 @@ */ namespace Magento\Customer\Block\Form; +use Magento\Customer\Block\DataProviders\AddressAttributeData; +use Magento\Framework\View\Element\Template; +use Magento\TestFramework\Helper\Bootstrap; + /** * Test class for \Magento\Customer\Block\Form\Register * @@ -15,14 +19,15 @@ class RegisterTest extends \PHPUnit\Framework\TestCase /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testCompanyDefault() + public function testCompanyDefault(): void { /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class - )->setTemplate('Magento_Customer::form/register.phtml') - ->setShowAddressFields(true); + $block = Bootstrap::getObjectManager()->create(Register::class) + ->setTemplate('Magento_Customer::form/register.phtml') + ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertContains('title="Company"', $block->toHtml()); } @@ -30,14 +35,16 @@ public function testCompanyDefault() /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testTelephoneDefault() + public function testTelephoneDefault(): void { /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class + $block = Bootstrap::getObjectManager()->create( + Register::class )->setTemplate('Magento_Customer::form/register.phtml') ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertContains('title="Phone Number"', $block->toHtml()); } @@ -45,14 +52,16 @@ public function testTelephoneDefault() /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testFaxDefault() + public function testFaxDefault(): void { /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class + $block = Bootstrap::getObjectManager()->create( + Register::class )->setTemplate('Magento_Customer::form/register.phtml') ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertNotContains('title="Fax"', $block->toHtml()); } @@ -60,21 +69,23 @@ public function testFaxDefault() /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testCompanyDisabled() + public function testCompanyDisabled(): void { /** @var \Magento\Customer\Model\Attribute $model */ - $model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $model = Bootstrap::getObjectManager()->create( \Magento\Customer\Model\Attribute::class ); $model->loadByCode('customer_address', 'company')->setIsVisible('0'); $model->save(); /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class + $block = Bootstrap::getObjectManager()->create( + Register::class )->setTemplate('Magento_Customer::form/register.phtml') ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertNotContains('title="Company"', $block->toHtml()); } @@ -82,21 +93,23 @@ public function testCompanyDisabled() /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testTelephoneDisabled() + public function testTelephoneDisabled(): void { /** @var \Magento\Customer\Model\Attribute $model */ - $model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $model = Bootstrap::getObjectManager()->create( \Magento\Customer\Model\Attribute::class ); $model->loadByCode('customer_address', 'telephone')->setIsVisible('0'); $model->save(); /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class + $block = Bootstrap::getObjectManager()->create( + Register::class )->setTemplate('Magento_Customer::form/register.phtml') ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertNotContains('title="Phone Number"', $block->toHtml()); } @@ -104,29 +117,46 @@ public function testTelephoneDisabled() /** * @magentoAppIsolation enabled * @magentoDbIsolation enabled + * @return void */ - public function testFaxEnabled() + public function testFaxEnabled(): void { /** @var \Magento\Customer\Model\Attribute $model */ - $model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $model = Bootstrap::getObjectManager()->create( \Magento\Customer\Model\Attribute::class ); $model->loadByCode('customer_address', 'fax')->setIsVisible('1'); $model->save(); /** @var \Magento\Customer\Block\Widget\Company $block */ - $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Block\Form\Register::class + $block = Bootstrap::getObjectManager()->create( + Register::class )->setTemplate('Magento_Customer::form/register.phtml') ->setShowAddressFields(true); + $this->setAttributeDataProvider($block); $this->assertContains('title="Fax"', $block->toHtml()); } + /** + * @inheritdoc + */ protected function tearDown() { /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); $eavConfig->clear(); } + + /** + * Set attribute data provider. + * + * @param Template $block + * @return void + */ + private function setAttributeDataProvider(Template $block): void + { + $attributeData = Bootstrap::getObjectManager()->get(AddressAttributeData::class); + $block->setAttributeData($attributeData); + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index eca5cf79c0664..186d9c8f87dc2 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -55,6 +55,9 @@ public function testIndexAction() $this->assertContains('Green str, 67', $body); } + /** + * @return void + */ public function testCreateAction() { $this->dispatch('customer/account/create'); @@ -219,6 +222,7 @@ public function testConfirmActionAlreadyActive() public function testNoFormKeyCreatePostAction() { $this->fillRequestWithAccountData('test1@email.com'); + $this->getRequest()->setPostValue('form_key', null); $this->dispatch('customer/account/createPost'); $this->assertNull($this->getCustomerByEmail('test1@email.com')); @@ -521,7 +525,7 @@ public function testEditPostAction() $this->dispatch('customer/account/editPost'); - $this->assertRedirect($this->stringEndsWith('customer/account/')); + $this->assertRedirect($this->stringContains('customer/account/')); $this->assertSessionMessages( $this->equalTo(['You saved the account information.']), MessageInterface::TYPE_SUCCESS @@ -569,7 +573,7 @@ public function testChangePasswordEditPostAction() $this->dispatch('customer/account/editPost'); - $this->assertRedirect($this->stringEndsWith('customer/account/')); + $this->assertRedirect($this->stringContains('customer/account/')); $this->assertSessionMessages( $this->equalTo(['You saved the account information.']), MessageInterface::TYPE_SUCCESS @@ -602,7 +606,7 @@ public function testMissingDataEditPostAction() $this->dispatch('customer/account/editPost'); - $this->assertRedirect($this->stringEndsWith('customer/account/edit/')); + $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( $this->equalTo(['"Email" is not a valid email address.']), MessageInterface::TYPE_ERROR @@ -632,7 +636,7 @@ public function testWrongPasswordEditPostAction() $this->dispatch('customer/account/editPost'); - $this->assertRedirect($this->stringEndsWith('customer/account/edit/')); + $this->assertRedirect($this->stringContains('customer/account/edit/')); // Not sure if its the most secure message. Not changing the behavior for now in the new AccountManagement APIs. $this->assertSessionMessages( $this->equalTo(["The password doesn't match this account. Verify the password and try again."]), @@ -661,7 +665,7 @@ public function testWrongConfirmationEditPostAction() $this->dispatch('customer/account/editPost'); - $this->assertRedirect($this->stringEndsWith('customer/account/edit/')); + $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( $this->equalTo(['Password confirmation doesn\'t match entered password.']), MessageInterface::TYPE_ERROR diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php index 4c30adb6894e2..32a622d4aa654 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AddressTest.php @@ -18,6 +18,9 @@ class AddressTest extends \Magento\TestFramework\TestCase\AbstractController /** @var FormKey */ private $formKey; + /** + * {@inheritDoc} + */ protected function setUp() { parent::setUp(); @@ -150,8 +153,8 @@ public function testFailedFormPostAction() $this->equalTo( [ 'One or more input exceptions have occurred.', - '"street" is required. Enter and try again.', - '"city" is required. Enter and try again.', + '"street" is required. Enter and try again.', + '"city" is required. Enter and try again.', ] ), \Magento\Framework\Message\MessageInterface::TYPE_ERROR diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php index ef5b4cae5ff16..8ee12fa1aa8d5 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php @@ -3,16 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Controller\Adminhtml\Index; -use Magento\TestFramework\Helper\Bootstrap; +use Magento\Backend\Model\Session; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\AbstractBackendController; /** * @magentoAppArea adminhtml */ -class MassAssignGroupTest extends \Magento\TestFramework\TestCase\AbstractBackendController +class MassAssignGroupTest extends AbstractBackendController { /** * Base controller URL @@ -29,9 +34,7 @@ class MassAssignGroupTest extends \Magento\TestFramework\TestCase\AbstractBacken protected function setUp() { parent::setUp(); - $this->customerRepository = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\CustomerRepositoryInterface::class - ); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); } protected function tearDown() @@ -39,75 +42,97 @@ protected function tearDown() /** * Unset customer data */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + Bootstrap::getObjectManager()->get(Session::class)->setCustomerData(null); /** * Unset messages */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); + Bootstrap::getObjectManager()->get(Session::class)->getMessages(true); } /** - * @magentoDataFixture Magento/Customer/_files/customer.php + * Tests os update a single customer record. + * + * @magentoDataFixture Magento/Customer/_files/five_repository_customers.php + * @magentoDbIsolation disabled */ public function testMassAssignGroupAction() { - $customer = $this->customerRepository->getById(1); + $customerEmail = 'customer1@example.com'; + /** @var CustomerInterface $customer */ + $customer = $this->customerRepository->get($customerEmail); $this->assertEquals(1, $customer->getGroupId()); - $this->getRequest() - ->setParam('group', 0) - ->setPostValue('namespace', 'customer_listing') - ->setPostValue('selected', [1]); + $params = [ + 'group' => 0, + 'namespace' => 'customer_listing', + 'selected' => [$customer->getId()] + ]; + + $this->getRequest()->setParams($params); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( - $this->equalTo(['A total of 1 record(s) were updated.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + self::equalTo(['A total of 1 record(s) were updated.']), + MessageInterface::TYPE_SUCCESS ); $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); - $customer = $this->customerRepository->getById(1); + $customer = $this->customerRepository->get($customerEmail); $this->assertEquals(0, $customer->getGroupId()); } /** - * @magentoDataFixture Magento/Customer/_files/twenty_one_customers.php + * Tests os update a multiple customer records. + * + * @magentoDataFixture Magento/Customer/_files/five_repository_customers.php + * @magentoDbIsolation disabled */ public function testLargeGroupMassAssignGroupAction() { - - for ($i = 1; $i < 22; $i++) { - $customer = $this->customerRepository->getById($i); + $ids = []; + for ($i = 1; $i <= 5; $i++) { + /** @var CustomerInterface $customer */ + $customer = $this->customerRepository->get('customer' . $i . '@example.com'); $this->assertEquals(1, $customer->getGroupId()); + $ids[] = $customer->getId(); } - $this->getRequest() - ->setParam('group', 0) - ->setPostValue('namespace', 'customer_listing') - ->setPostValue('selected', [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]); + $params = [ + 'group' => 0, + 'namespace' => 'customer_listing', + 'selected' => $ids, + ]; + + $this->getRequest()->setParams($params); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( - $this->equalTo(['A total of 21 record(s) were updated.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + self::equalTo(['A total of 5 record(s) were updated.']), + MessageInterface::TYPE_SUCCESS ); $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); - for ($i = 1; $i < 22; $i++) { - $customer = $this->customerRepository->getById($i); + for ($i = 1; $i < 5; $i++) { + /** @var CustomerInterface $customer */ + $customer = $this->customerRepository->get('customer' . $i . '@example.com'); $this->assertEquals(0, $customer->getGroupId()); } } /** * Valid group Id but no customer Ids specified + * * @magentoDbIsolation enabled */ public function testMassAssignGroupActionNoCustomerIds() { - $this->getRequest()->setParam('group', 0)->setPostValue('namespace', 'customer_listing'); + $params = [ + 'group' => 0, + 'namespace' => 'customer_listing', + ]; + $this->getRequest()->setParams($params); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( $this->equalTo(['An item needs to be selected. Select and try again.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR + MessageInterface::TYPE_ERROR ); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php index b7aefe7c31707..940f7c84325f6 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php @@ -3,61 +3,162 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Backend\Model\Session; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use PHPUnit\Framework\Constraint\Constraint; +use Magento\Framework\Message\MessageInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\AbstractBackendController; /** * @magentoAppArea adminhtml */ -class MassDeleteTest extends \Magento\TestFramework\TestCase\AbstractBackendController +class MassDeleteTest extends AbstractBackendController { + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + /** * Base controller URL * * @var string */ - protected $baseControllerUrl = 'http://localhost/index.php/backend/customer/index/index'; + private $baseControllerUrl = 'http://localhost/index.php/backend/customer/index/index'; + + protected function setUp() + { + parent::setUp(); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + } protected function tearDown() { /** * Unset customer data */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + Bootstrap::getObjectManager()->get(Session::class)->setCustomerData(null); /** * Unset messages */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); + Bootstrap::getObjectManager()->get(Session::class)->getMessages(true); } /** - * @magentoDataFixture Magento/Customer/_files/customer.php + * Validates failure attempts to delete customers from grid. + * + * @param array|null $ids + * @param Constraint $constraint + * @param string|null $messageType + * @magentoDataFixture Magento/Customer/_files/five_repository_customers.php + * @magentoDbIsolation disabled + * @dataProvider failedRequestDataProvider */ - public function testMassDeleteAction() + public function testFailedMassDeleteAction($ids, Constraint $constraint, $messageType) { - $this->getRequest()->setPostValue('selected', [1])->setPostValue('namespace', 'customer_listing'); - $this->dispatch('backend/customer/index/massDelete'); - $this->assertSessionMessages( - $this->equalTo(['A total of 1 record(s) were deleted.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + $this->massDeleteAssertions($ids, $constraint, $messageType); + } + + /** + * Validates success attempt to delete customer from grid. + * + * @param array $emails + * @param Constraint $constraint + * @param string $messageType + * @magentoDataFixture Magento/Customer/_files/five_repository_customers.php + * @magentoDbIsolation disabled + * @dataProvider successRequestDataProvider + */ + public function testSuccessMassDeleteAction(array $emails, Constraint $constraint, string $messageType) + { + $ids = []; + foreach ($emails as $email) { + /** @var CustomerInterface $customer */ + $customer = $this->customerRepository->get($email); + $ids[] = $customer->getId(); + } + + $this->massDeleteAssertions( + $ids, + $constraint, + $messageType ); - $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); } /** - * Valid group Id but no customer Ids specified - * @magentoDbIsolation enabled + * Performs required request and assertions. + * + * @param array|null $ids + * @param Constraint $constraint + * @param string|null $messageType */ - public function testMassDeleteActionNoCustomerIds() + private function massDeleteAssertions($ids, Constraint $constraint, $messageType) { - $this->getRequest()->setPostValue('namespace', 'customer_listing'); + $requestData = [ + 'selected' => $ids, + 'namespace' => 'customer_listing', + ]; + + $this->getRequest()->setParams($requestData); $this->dispatch('backend/customer/index/massDelete'); $this->assertSessionMessages( - $this->equalTo(['An item needs to be selected. Select and try again.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR + $constraint, + $messageType ); + $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); + } + + /** + * Provides sets of data for unsuccessful attempts. + * + * @return array + */ + public function failedRequestDataProvider(): array + { + return [ + [ + 'ids' => [], + 'constraint' => self::equalTo(['An item needs to be selected. Select and try again.']), + 'messageType' => MessageInterface::TYPE_ERROR, + ], + [ + 'ids' => [111], + 'constraint' => self::isEmpty(), + 'messageType' => null, + ], + [ + 'ids' => null, + 'constraint' => self::equalTo(['An item needs to be selected. Select and try again.']), + 'messageType' => MessageInterface::TYPE_ERROR, + ] + ]; + } + + /** + * Provides sets of data for successful attempts. + * + * @return array + */ + public function successRequestDataProvider(): array + { + return [ + [ + 'customerEmails' => ['customer1@example.com'], + 'constraint' => self::equalTo(['A total of 1 record(s) were deleted.']), + 'messageType' => MessageInterface::TYPE_SUCCESS, + ], + [ + 'customerEmails' => ['customer2@example.com', 'customer3@example.com'], + 'constraint' => self::equalTo(['A total of 2 record(s) were deleted.']), + 'messageType' => MessageInterface::TYPE_SUCCESS, + ], + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php index d9880b2dc741a..c2fc7b1b58756 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php @@ -3,11 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Backend\Model\Session; +use Magento\Framework\Message\MessageInterface; use Magento\Newsletter\Model\Subscriber; +use Magento\Newsletter\Model\SubscriberFactory; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; /** * @magentoAppArea adminhtml @@ -26,58 +32,90 @@ protected function tearDown() /** * Unset customer data */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + Bootstrap::getObjectManager()->get(Session::class)->setCustomerData(null); /** * Unset messages */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); + Bootstrap::getObjectManager()->get(Session::class)->getMessages(true); } /** - * @magentoDataFixture Magento/Customer/_files/two_customers.php + * Tests subscriber status of customers. + * + * @magentoDataFixture Magento/Customer/_files/five_repository_customers.php + * @magentoDbIsolation disabled */ public function testMassSubscriberAction() { - // Pre-condition - /** @var \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory */ - $subscriberFactory = Bootstrap::getObjectManager()->get(\Magento\Newsletter\Model\SubscriberFactory::class); - $this->assertNull($subscriberFactory->create()->loadByCustomerId(1)->getSubscriberStatus()); - $this->assertNull($subscriberFactory->create()->loadByCustomerId(2)->getSubscriberStatus()); - // Setup - $this->getRequest()->setPostValue('selected', [1, 2])->setPostValue('namespace', 'customer_listing'); + /** @var SubscriberFactory $subscriberFactory */ + $subscriberFactory = Bootstrap::getObjectManager()->get(SubscriberFactory::class); + $customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + + $this->assertNull( + $subscriberFactory->create() + ->loadByEmail('customer1@example.com') + ->getSubscriberStatus() + ); + $this->assertNull( + $subscriberFactory->create() + ->loadByEmail('customer2@example.com') + ->getSubscriberStatus() + ); + + /** @var CustomerInterface $customer1 */ + $customer1 = $customerRepository->get('customer1@example.com'); + /** @var CustomerInterface $customer2 */ + $customer2 = $customerRepository->get('customer2@example.com'); + + $params = [ + 'selected' => [ + $customer1->getId(), + $customer2->getId(), + ], + 'namespace' => 'customer_listing', + ]; + $this->getRequest()->setParams($params); - // Test $this->dispatch('backend/customer/index/massSubscribe'); // Assertions $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); $this->assertSessionMessages( - $this->equalTo(['A total of 2 record(s) were updated.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + self::equalTo(['A total of 2 record(s) were updated.']), + MessageInterface::TYPE_SUCCESS ); $this->assertEquals( Subscriber::STATUS_SUBSCRIBED, - $subscriberFactory->create()->loadByCustomerId(1)->getSubscriberStatus() + $subscriberFactory->create() + ->loadByEmail('customer1@example.com') + ->getSubscriberStatus() ); $this->assertEquals( Subscriber::STATUS_SUBSCRIBED, - $subscriberFactory->create()->loadByCustomerId(2)->getSubscriberStatus() + $subscriberFactory->create() + ->loadByEmail('customer2@example.com') + ->getSubscriberStatus() ); } /** + * @magentoAppIsolation enabled * @magentoDbIsolation enabled */ public function testMassSubscriberActionNoSelection() { - $this->getRequest()->setPostValue('namespace', 'customer_listing'); + $params = [ + 'namespace' => 'customer_listing' + ]; + + $this->getRequest()->setParams($params); $this->dispatch('backend/customer/index/massSubscribe'); $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); $this->assertSessionMessages( - $this->equalTo(['An item needs to be selected. Select and try again.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR + self::equalTo(['An item needs to be selected. Select and try again.']), + MessageInterface::TYPE_ERROR ); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index b942365d64a75..6448816c9345c 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -44,6 +44,9 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle /** @var \Magento\TestFramework\ObjectManager */ protected $objectManager; + /** + * {@inheritDoc} + */ protected function setUp() { parent::setUp(); @@ -67,6 +70,9 @@ protected function setUp() ); } + /** + * {@inheritDoc} + */ protected function tearDown() { /** @@ -522,7 +528,10 @@ public function testEditAction() $this->assertContains('<h1 class="page-title">test firstname test lastname</h1>', $body); } - public function testNewAction() + /** + * Tests new action + */ + public function testNewAction(): void { $this->dispatch('backend/customer/index/edit'); $body = $this->getResponse()->getBody(); @@ -717,12 +726,9 @@ public function testValidateCustomerWithAddressFailure() $this->assertContains('{"error":true,"messages":', $body); $this->assertContains('\"First Name\" is a required value', $body); - $this->assertContains('\"First Name\" length must be equal or greater than 1 characters', $body); $this->assertContains('\"Last Name\" is a required value.', $body); - $this->assertContains('\"Last Name\" length must be equal or greater than 1 characters.', $body); $this->assertContains('\"Country\" is a required value.', $body); $this->assertContains('\"Phone Number\" is a required value.', $body); - $this->assertContains('\"Phone Number\" length must be equal or greater than 1 characters.', $body); } /** diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php index 3ed330ed98d6b..336b438661705 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php @@ -262,7 +262,7 @@ public function testGetCustomerAttributeMetadata() $this->assertEquals( $attributeMetadata, $attributeMetadata1, - 'Attribute metadata from the the same service became different after getAttributeCode was called' + 'Attribute metadata from the same service became different after getAttributeCode was called' ); // Verify the consistency of attribute metadata from two services // after getAttributeCode was called diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_website_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_website_rollback.php new file mode 100644 index 0000000000000..2dbd1381b6f3c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_website_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\ObjectManagerInterface $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Store\Model\StoreManager $store */ +$store = $objectManager->get(\Magento\Store\Model\StoreManager::class); + +/** @var $customer \Magento\Customer\Model\Customer*/ +$customer = $objectManager->create(\Magento\Customer\Model\Customer::class); +$customer->setWebsiteId($store->getDefaultStoreView()->getWebsiteId()); +$customer->loadByEmail('john.doe@magento.com'); +if ($customer->getId()) { + $customer->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers.php b/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers.php new file mode 100644 index 0000000000000..1722e471a5bbc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Customer\Model\Customer; +use Magento\Eav\Model\Config as EavModelConfig; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); +/** @var CustomerInterfaceFactory $customerFactory */ +$customerFactory = $objectManager->get(CustomerInterfaceFactory::class); + +for ($i = 1; $i <= 5; $i++) { + /** @var CustomerInterface $customer */ + $customer = $customerFactory->create(); + $customer->setFirstname('John') + ->setGroupId(1) + ->setLastname('Smith') + ->setWebsiteId(1) + ->setEmail('customer'.$i.'@example.com'); + try { + $customerRepository->save($customer, 'password'); + } catch (\Exception $e) { + } +} + +/** @var EavModelConfig $eavConfig */ +$eavConfig = $objectManager->get(EavModelConfig::class); +$eavConfig->clear(); + +/** @var IndexerRegistry $indexerRegistry */ +$indexerRegistry = $objectManager->create(IndexerRegistry::class); +/** @var IndexerInterface $indexer */ +$indexer = $indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); +try { + $indexer->reindexAll(); +} catch (\Exception $e) { +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers_rollback.php new file mode 100644 index 0000000000000..5272d9cdbf06d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/five_repository_customers_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Eav\Model\Config as EavModelConfig; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var CustomerRepositoryInterface $repository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); + +for ($i = 1; $i <= 5; $i++) { + try { + /** @var CustomerInterface $customer */ + $customer = $customerRepository->get('customer'.$i.'@example.com'); + $customerRepository->delete($customer); + } catch (\Exception $e) { + } +} + +/** @var EavModelConfig $eavConfig */ +$eavConfig = $objectManager->get(EavModelConfig::class); +$eavConfig->clear(); diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php index b65aa7734f2da..45d84336337c5 100644 --- a/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/ValidatorTest.php @@ -130,7 +130,7 @@ public function getPostcodesDataProvider() ['countryId' => 'IS', 'postcode' => '123'], ['countryId' => 'IN', 'postcode' => '123456'], ['countryId' => 'ID', 'postcode' => '12345'], - ['countryId' => 'IL', 'postcode' => '12345'], + ['countryId' => 'IL', 'postcode' => '1234567'], ['countryId' => 'IT', 'postcode' => '12345'], ['countryId' => 'JP', 'postcode' => '123-4567'], ['countryId' => 'JP', 'postcode' => '1234567'], diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/ObserverTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/ObserverTest.php deleted file mode 100644 index c3549a60e2444..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Directory/Model/ObserverTest.php +++ /dev/null @@ -1,90 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Directory\Model; - -use Magento\Framework\ObjectManagerInterface; -use Magento\Store\Model\ScopeInterface; -use Magento\TestFramework\Helper\Bootstrap; - -/** - * Integration test for \Magento\Directory\Model\Observer - */ -class ObserverTest extends \PHPUnit\Framework\TestCase -{ - /** @var ObjectManagerInterface */ - protected $objectManager; - - /** @var Observer */ - protected $observer; - - /** @var \Magento\Framework\App\MutableScopeConfig */ - protected $scopeConfig; - - /** @var string */ - protected $baseCurrency = 'USD'; - - /** @var string */ - protected $baseCurrencyPath = 'currency/options/base'; - - /** @var string */ - protected $allowedCurrenciesPath = 'currency/options/allow'; - - /** @var \Magento\Config\Model\ResourceModel\Config */ - protected $configResource; - - public function setUp() - { - $this->objectManager = Bootstrap::getObjectManager(); - - $this->scopeConfig = $this->objectManager->create(\Magento\Framework\App\MutableScopeConfig::class); - $this->scopeConfig->setValue(Observer::IMPORT_ENABLE, 1, ScopeInterface::SCOPE_STORE); - $this->scopeConfig->setValue(Observer::CRON_STRING_PATH, 'cron-string-path', ScopeInterface::SCOPE_STORE); - $this->scopeConfig->setValue(Observer::IMPORT_SERVICE, 'webservicex', ScopeInterface::SCOPE_STORE); - - $this->configResource = $this->objectManager->get(\Magento\Config\Model\ResourceModel\Config::class); - $this->configResource->saveConfig( - $this->baseCurrencyPath, - $this->baseCurrency, - ScopeInterface::SCOPE_STORE, - 0 - ); - - $this->observer = $this->objectManager->create(\Magento\Directory\Model\Observer::class); - } - - public function testScheduledUpdateCurrencyRates() - { - //skipping test if service is unavailable - $url = str_replace( - '{{CURRENCY_FROM}}', - 'USD', - \Magento\Directory\Model\Currency\Import\Webservicex::CURRENCY_CONVERTER_URL - ); - $url = str_replace('{{CURRENCY_TO}}', 'GBP', $url); - try { - file_get_contents($url); - } catch (\PHPUnit\Framework\Exception $e) { - $this->markTestSkipped('http://www.webservicex.net is unavailable '); - } - - $allowedCurrencies = 'USD,GBP,EUR'; - $this->configResource->saveConfig( - $this->allowedCurrenciesPath, - $allowedCurrencies, - ScopeInterface::SCOPE_STORE, - 0 - ); - $this->observer->scheduledUpdateCurrencyRates(null); - /** @var Currency $currencyResource */ - $currencyResource = $this->objectManager - ->create(\Magento\Directory\Model\CurrencyFactory::class) - ->create() - ->getResource(); - $rates = $currencyResource->getCurrencyRates($this->baseCurrency, explode(',', $allowedCurrencies)); - $this->assertNotEmpty($rates); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_files.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_files.php index cf0da5599914f..86aa61a99e1e8 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_files.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_files.php @@ -154,3 +154,10 @@ $product->setTypeHasRequiredOptions(false)->setRequiredOptions(false); } $product->save(); + +$stockRegistry = $objectManager->get(\Magento\CatalogInventory\Api\StockRegistryInterface::class); +$stockItem = $stockRegistry->getStockItem($product->getId()); +$stockItem->setUseConfigManageStock(true); +$stockItem->setQty(100); +$stockItem->setIsInStock(true); +$stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); diff --git a/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/AttributeTest.php new file mode 100644 index 0000000000000..2750e2a768aab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Model/Entity/AttributeTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Eav\Model\Entity; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Locale\ResolverInterface; + +class AttributeTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Attribute + */ + private $attribute; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ResolverInterface + */ + private $_localeResolver; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->attribute = $this->objectManager->get(Attribute::class); + $this->_localeResolver = $this->objectManager->get(ResolverInterface::class); + } + + /** + * {@inheritdoc} + */ + protected function tearDown() + { + $this->attribute = null; + $this->objectManager = null; + $this->_localeResolver = null; + } + + /** + * @param string $defaultValue + * @param string $backendType + * @param string $locale + * @param string $expected + * @dataProvider beforeSaveDataProvider + * @throws + */ + public function testBeforeSave($defaultValue, $backendType, $locale, $expected) + { + $this->attribute->setDefaultValue($defaultValue); + $this->attribute->setBackendType($backendType); + $this->_localeResolver->setLocale($locale); + $this->attribute->beforeSave(); + + $this->assertEquals($expected, $this->attribute->getDefaultValue()); + } + + /** + * Data provider for beforeSaveData. + * + * @return array + */ + public function beforeSaveDataProvider() + { + return [ + ['21/07/18', 'datetime', 'en_AU', '2018-07-21 00:00:00'], + ['07/21/18', 'datetime', 'en_US', '2018-07-21 00:00:00'], + ['21/07/18', 'datetime', 'fr_FR', '2018-07-21 00:00:00'], + ['21/07/18', 'datetime', 'de_DE', '2018-07-21 00:00:00'], + ['21/07/18', 'datetime', 'uk_UA', '2018-07-21 00:00:00'], + ['100.50', 'decimal', 'en_US', '100.50'], + ['100,50', 'decimal', 'uk_UA', '100.5'], + ]; + } + + /** + * @param string $defaultValue + * @param string $backendType + * @param string $locale + * @param string $expected + * @dataProvider beforeSaveErrorDataDataProvider + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testBeforeSaveErrorData($defaultValue, $backendType, $locale, $expected) + { + $this->attribute->setDefaultValue($defaultValue); + $this->attribute->setBackendType($backendType); + $this->_localeResolver->setLocale($locale); + $this->attribute->beforeSave(); + + $this->expectExceptionMessage($expected); + } + + /** + * Data provider for beforeSaveData with error result. + * + * @return array + */ + public function beforeSaveErrorDataDataProvider() + { + return [ + 'wrong date for Australia' => ['32/38', 'datetime', 'en_AU', 'Invalid default date'], + 'wrong date for States' => ['32/38', 'datetime', 'en_US', 'Invalid default date'], + 'wrong decimal separator for US' => ['100,50', 'decimal', 'en_US', 'Invalid default decimal value'], + 'wrong decimal separator for UA' => ['100.50', 'decimal', 'uk_UA', 'Invalid default decimal value'], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Model/ResourceModel/Entity/Attribute/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Eav/Model/ResourceModel/Entity/Attribute/CollectionTest.php index e39f1e2fd8390..c273e87f6d738 100644 --- a/dev/tests/integration/testsuite/Magento/Eav/Model/ResourceModel/Entity/Attribute/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Eav/Model/ResourceModel/Entity/Attribute/CollectionTest.php @@ -52,6 +52,20 @@ public function testSetAttributeGroupFilter() $this->assertEquals([$includeGroupId], $groups); } + /** + * Test if getAllIds method return results after using setInAllAttributeSetsFilter method + * + * @covers \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection::setInAllAttributeSetsFilter() + * @covers \Magento\Eav\Model\ResourceModel\Entity\Attribute\Collection::getAllIds() + */ + public function testSetInAllAttributeSetsFilterWithGetAllIds() + { + $sets = [1]; + $this->_model->setInAllAttributeSetsFilter($sets); + $attributeIds = $this->_model->getAllIds(); + $this->assertGreaterThan(0, count($attributeIds)); + } + /** * Returns array of group ids, present in collection attributes * diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php index c199214f68578..496d03dbd7c58 100644 --- a/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Model/ResourceModel/Key/ChangeTest.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\EncryptionKey\Model\ResourceModel\Key; class ChangeTest extends \PHPUnit\Framework\TestCase @@ -79,7 +81,7 @@ public function testChangeEncryptionKey() ) ); $this->assertNotContains($testValue, $values1); - $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9+/]+=)|', current($values1)); + $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9+/]+=*)|', current($values1)); // Verify that the credit card number has been encrypted $values2 = $connection->fetchPairs( @@ -89,7 +91,7 @@ public function testChangeEncryptionKey() ) ); $this->assertNotContains('1111111111', $values2); - $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9]+:)([a-zA-Z0-9+/]+=)|', current($values1)); + $this->assertRegExp('|([0-9]+:)([0-9]+:)([a-zA-Z0-9+/]+=*)|', current($values2)); /** clean up */ $select = $connection->select()->from($configModel->getMainTable())->where('path=?', $testPath); diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php new file mode 100644 index 0000000000000..3a47692bdb932 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\EncryptionKey\Setup\Patch\Data; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Encryption\Encryptor; + +class SodiumChachaPatchTest extends \PHPUnit\Framework\TestCase +{ + const PATH_KEY = 'crypt/key'; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var DeploymentConfig + */ + private $deployConfig; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->deployConfig = $this->objectManager->get(DeploymentConfig::class); + } + + public function testChangeEncryptionKey() + { + $testPath = 'test/config'; + $testValue = 'test'; + + $structureMock = $this->createMock(\Magento\Config\Model\Config\Structure\Proxy::class); + $structureMock->expects($this->once()) + ->method('getFieldPathsByAttribute') + ->will($this->returnValue([$testPath])); + + /** @var \Magento\Config\Model\ResourceModel\Config $configModel */ + $configModel = $this->objectManager->create(\Magento\Config\Model\ResourceModel\Config::class); + $configModel->saveConfig($testPath, $this->legacyEncrypt($testValue), 'default', 0); + + /** @var \Magento\EncryptionKey\Setup\Patch\Data\SodiumChachaPatch $patch */ + $patch = $this->objectManager->create( + \Magento\EncryptionKey\Setup\Patch\Data\SodiumChachaPatch::class, + [ + 'structure' => $structureMock, + ] + ); + $patch->apply(); + + $connection = $configModel->getConnection(); + $values = $connection->fetchPairs( + $connection->select()->from( + $configModel->getMainTable(), + ['config_id', 'value'] + )->where( + 'path IN (?)', + [$testPath] + )->where( + 'value NOT LIKE ?', + '' + ) + ); + + /** @var \Magento\Framework\Encryption\EncryptorInterface $encyptor */ + $encyptor = $this->objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); + + $rawConfigValue = array_pop($values); + + $this->assertNotEquals($testValue, $rawConfigValue); + $this->assertStringStartsWith('0:' . Encryptor::CIPHER_LATEST . ':', $rawConfigValue); + $this->assertEquals($testValue, $encyptor->decrypt($rawConfigValue)); + } + + private function legacyEncrypt(string $data): string + { + // @codingStandardsIgnoreStart + $handle = @mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, ''); + $initVectorSize = @mcrypt_enc_get_iv_size($handle); + $initVector = str_repeat("\0", $initVectorSize); + @mcrypt_generic_init($handle, $this->deployConfig->get(static::PATH_KEY), $initVector); + + $encrpted = @mcrypt_generic($handle, $data); + + @mcrypt_generic_deinit($handle); + @mcrypt_module_close($handle); + // @codingStandardsIgnoreEnd + + return '0:' . Encryptor::CIPHER_RIJNDAEL_256 . ':' . base64_encode($encrpted); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php new file mode 100644 index 0000000000000..9ac778da91f29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + * + */ +declare(strict_types=1); + +namespace Magento\Framework\App\Filesystem; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Framework\Filesystem; +use Magento\TestFramework\Helper\Bootstrap; +use Zend\Http\Header\ContentType; + +/** + * Class CreatePdfFileTest + * + * Integration test for testing a file creation from string + */ +class CreatePdfFileTest extends \PHPUnit\Framework\TestCase +{ + /** + * @throws \Exception + */ + public function testGenerateFileFromString() + { + $objectManager = Bootstrap::getObjectManager(); + /** @var FileFactory $fileFactory */ + $fileFactory = $objectManager->get(FileFactory::class); + /** @var Filesystem $filesystem */ + $filesystem = $objectManager->get(Filesystem::class); + $filename = 'test.pdf'; + $contentType = 'application/pdf'; + $fileContent = ['type' => 'string', 'value' => '']; + $response = $fileFactory->create($filename, $fileContent, DirectoryList::VAR_DIR, $contentType); + /** @var ContentType $contentTypeHeader */ + $contentTypeHeader = $response->getHeader('Content-type'); + + /* Check the system returns the correct type */ + self::assertEquals("Content-Type: $contentType", $contentTypeHeader->toString()); + + $varDirectory = $filesystem->getDirectoryRead(DirectoryList::VAR_DIR); + $varDirectory->isFile($filename); + + /* Check the file is generated */ + self::assertTrue($varDirectory->isFile($filename)); + + /* Check the file is removed after generation if the corresponding option is set */ + $fileContent = ['type' => 'string', 'value' => '', 'rm' => true]; + $fileFactory->create($filename, $fileContent, DirectoryList::VAR_DIR, $contentType); + + self::assertFalse($varDirectory->isFile($filename)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/DirectoryResolverTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/DirectoryResolverTest.php index 8f1f9e92972e5..90d8473032c69 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/DirectoryResolverTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/DirectoryResolverTest.php @@ -52,7 +52,7 @@ public function testValidatePath($path, $directoryConfig, $expectation) { $directory = $this->filesystem ->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); - $path = $directory->getAbsolutePath($path); + $path = $directory->getAbsolutePath() .'/' .$path; $this->assertEquals($expectation, $this->directoryResolver->validatePath($path, $directoryConfig)); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php new file mode 100644 index 0000000000000..9246be52f41bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php @@ -0,0 +1,269 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Phrase; +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http as HttpRequest; +use Zend\Stdlib\Parameters; +use Magento\Framework\App\Response\Http as HttpResponse; +use Magento\Framework\App\Response\HttpFactory as HttpResponseFactory; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CsrfValidatorTest extends TestCase +{ + private const AWARE_URL = 'test/1'; + + private const AWARE_VALIDATION_PARAM = 'test_param'; + + private const AWARE_MESSAGE = 'custom validation failed'; + + /** + * @var ActionInterface + */ + private $mockUnawareAction; + + /** + * @var ActionInterface + */ + private $mockAwareAction; + + /** + * @var CsrfValidator + */ + private $validator; + + /** + * @var HttpRequest + */ + private $request; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @var HttpResponseFactory + */ + private $httpResponseFactory; + + /** + * @return ActionInterface + */ + private function createUnawareAction(): ActionInterface + { + return new class implements ActionInterface { + /** + * @inheritDoc + */ + public function execute() + { + throw new NotFoundException(new Phrase('Not implemented')); + } + }; + } + + /** + * @return ActionInterface + */ + private function createAwareAction(): ActionInterface + { + $u = self::AWARE_URL; + $m = self::AWARE_MESSAGE; + $p = self::AWARE_VALIDATION_PARAM; + + return new class($u, $m, $p) implements CsrfAwareActionInterface { + /** + * @var string + */ + private $url; + + /** + * @var string + */ + private $message; + + /** + * @var string + */ + private $param; + + /** + * @param string $url + * @param string $message + * @param string $param + */ + public function __construct( + string $url, + string $message, + string $param + ) { + $this->url = $url; + $this->message = $message; + $this->param = $param; + } + + /** + * @inheritDoc + */ + public function execute() + { + throw new NotFoundException(new Phrase('Not implemented')); + } + + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + /** @var RedirectFactory $redirectFactory */ + $redirectFactory = Bootstrap::getObjectManager() + ->get(RedirectFactory::class); + $redirect = $redirectFactory->create(); + $redirect->setUrl($this->url); + + return new InvalidRequestException( + $redirect, + [new Phrase($this->message)] + ); + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return (bool)$request->getParam($this->param); + } + }; + } + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->request = $objectManager->get(HttpRequest::class); + $this->validator = $objectManager->get(CsrfValidator::class); + $this->mockUnawareAction = $this->createUnawareAction(); + $this->mockAwareAction = $this->createAwareAction(); + $this->formKey = $objectManager->get(FormKey::class); + $this->httpResponseFactory = $objectManager->get( + HttpResponseFactory::class + ); + } + + /** + * @magentoAppArea global + */ + public function testValidateInWrongArea() + { + $this->request->setMethod(HttpRequest::METHOD_POST); + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @magentoAppArea frontend + */ + public function testValidateWithValidKey() + { + $this->request->setPost( + new Parameters(['form_key' => $this->formKey->getFormKey()]) + ); + $this->request->setMethod(HttpRequest::METHOD_POST); + + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @expectedException \Magento\Framework\App\Request\InvalidRequestException + * @magentoAppArea adminhtml + */ + public function testValidateWithInvalidKey() + { + $this->request->setPost( + new Parameters(['form_key' => $this->formKey->getFormKey() .'1']) + ); + $this->request->setMethod(HttpRequest::METHOD_POST); + + $this->validator->validate( + $this->request, + $this->mockUnawareAction + ); + } + + /** + * @magentoAppArea frontend + */ + public function testValidateInvalidWithAwareAction() + { + $this->request->setMethod(HttpRequest::METHOD_POST); + + /** @var InvalidRequestException|null $caught */ + $caught = null; + try { + $this->validator->validate( + $this->request, + $this->mockAwareAction + ); + } catch (InvalidRequestException $exception) { + $caught = $exception; + } + + $this->assertNotNull($caught); + $this->assertInstanceOf(Redirect::class, $caught->getReplaceResult()); + /** @var HttpResponse $response */ + $response = $this->httpResponseFactory->create(); + $caught->getReplaceResult()->renderResult($response); + $this->assertContains( + self::AWARE_URL, + $response->getHeaders()->toString() + ); + $this->assertCount(1, $caught->getMessages()); + $this->assertEquals( + self::AWARE_MESSAGE, + $caught->getMessages()[0]->getText() + ); + } + + /** + * @magentoAppArea frontend + */ + public function testValidateValidWithAwareAction() + { + $this->request->setMethod(HttpRequest::METHOD_POST); + $this->request->setPost( + new Parameters([self::AWARE_VALIDATION_PARAM => '1']) + ); + + $this->validator->validate( + $this->request, + $this->mockAwareAction + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/fixtures/theme/registration.php b/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/fixtures/theme/registration.php index f3b30797b93ed..c286f6c57d87f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/fixtures/theme/registration.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/fixtures/theme/registration.php @@ -6,4 +6,4 @@ use \Magento\Framework\Component\ComponentRegistrar; -ComponentRegistrar::register(ComponentRegistrar::THEME, 'frontent/Test/theme', __DIR__); +ComponentRegistrar::register(ComponentRegistrar::THEME, 'frontend/Test/theme', __DIR__); diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/patterns/paths_one.txt b/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/patterns/paths_one.txt index 8f2df480aabc5..f9cd49de77d67 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/patterns/paths_one.txt +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Utility/_files/patterns/paths_one.txt @@ -1,2 +1,2 @@ module Magento_Module * -theme frontent/Test/theme One* +theme frontend/Test/theme One* diff --git a/dev/tests/integration/testsuite/Magento/Framework/Console/CliTest.php b/dev/tests/integration/testsuite/Magento/Framework/Console/CliTest.php new file mode 100644 index 0000000000000..7a439d84ce563 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Console/CliTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Console; + +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\DeploymentConfig\FileReader; +use Magento\Framework\App\DeploymentConfig\Writer; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Filesystem; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +class CliTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var ConfigFilePool + */ + private $configFilePool; + + /** + * @var FileReader + */ + private $reader; + + /** + * @var Writer + */ + private $writer; + + /** + * @var array + */ + private $envConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->configFilePool = $this->objectManager->get(ConfigFilePool::class); + $this->filesystem = $this->objectManager->get(Filesystem::class); + $this->reader = $this->objectManager->get(FileReader::class); + $this->writer = $this->objectManager->get(Writer::class); + + $this->envConfig = $this->reader->load(ConfigFilePool::APP_ENV); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->filesystem->getDirectoryWrite(DirectoryList::CONFIG)->writeFile( + $this->configFilePool->getPath(ConfigFilePool::APP_ENV), + "<?php\n return array();\n" + ); + + $this->writer->saveConfig([ConfigFilePool::APP_ENV => $this->envConfig], true); + } + + /** + * Checks that settings from env.php config file are applied + * to created application instance. + * + * @param bool $isPub + * @param array $params + * @dataProvider documentRootIsPubProvider + */ + public function testDocumentRootIsPublic($isPub, $params) + { + $config = include __DIR__ . '/_files/env.php'; + $config['directories']['document_root_is_pub'] = $isPub; + $this->writer->saveConfig([ConfigFilePool::APP_ENV => $config], true); + + $cli = new Cli(); + $cliReflection = new \ReflectionClass($cli); + + $serviceManagerProperty = $cliReflection->getProperty('serviceManager'); + $serviceManagerProperty->setAccessible(true); + $serviceManager = $serviceManagerProperty->getValue($cli); + $deploymentConfig = $this->objectManager->get(DeploymentConfig::class); + $serviceManager->setAllowOverride(true); + $serviceManager->setService(DeploymentConfig::class, $deploymentConfig); + $serviceManagerProperty->setAccessible(false); + + $documentRootResolver = $cliReflection->getMethod('documentRootResolver'); + $documentRootResolver->setAccessible(true); + + self::assertEquals($params, $documentRootResolver->invoke($cli)); + } + + /** + * Provides document root setting and expecting + * properties for object manager creation. + * + * @return array + */ + public function documentRootIsPubProvider(): array + { + return [ + [true, [ + 'MAGE_DIRS' => [ + 'pub' => ['uri' => ''], + 'media' => ['uri' => 'media'], + 'static' => ['uri' => 'static'], + 'upload' => ['uri' => 'media/upload'] + ] + ]], + [false, []] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Console/_files/env.php b/dev/tests/integration/testsuite/Magento/Framework/Console/_files/env.php new file mode 100644 index 0000000000000..e314e7638c22c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Console/_files/env.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'backend' => [ + 'frontName' => 'admin', + ], + 'crypt' => [ + 'key' => 'some_key', + ], + 'session' => [ + 'save' => 'files', + ], + 'db' => [ + 'table_prefix' => '', + 'connection' => [], + ], + 'resource' => [], + 'x-frame-options' => 'SAMEORIGIN', + 'MAGE_MODE' => 'default', + 'cache_types' => [ + 'config' => 1, + 'layout' => 1, + 'block_html' => 1, + 'collections' => 1, + 'reflection' => 1, + 'db_ddl' => 1, + 'eav' => 1, + 'customer_notification' => 1, + 'config_integration' => 1, + 'config_integration_api' => 1, + 'full_page' => 1, + 'translate' => 1, + 'config_webservice' => 1, + ], + 'install' => [ + 'date' => 'Thu, 09 Feb 2017 14:28:00 +0000', + ], + 'directories' => [ + 'document_root_is_pub' => true + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php index f5b48e79b9860..12f398b705b2d 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Encryption/ModelTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Encryption; class ModelTest extends \PHPUnit\Framework\TestCase @@ -40,7 +42,16 @@ public function testEncryptDecrypt2() public function testValidateKey() { $validKey = md5(uniqid()); - $this->assertInstanceOf(\Magento\Framework\Encryption\Crypt::class, $this->_model->validateKey($validKey)); + $this->_model->validateKey($validKey); + } + + /** + * @expectedException \Exception + */ + public function testValidateKeyInvalid() + { + $invalidKey = '---- '; + $this->_model->validateKey($invalidKey); } public function testGetValidateHash() diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php index f43a523a12ed0..bc77eeb932c9a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php @@ -7,6 +7,7 @@ */ namespace Magento\Framework\Filesystem\Directory; +use Magento\Framework\Exception\ValidatorException; use Magento\TestFramework\Helper\Bootstrap; /** @@ -34,13 +35,66 @@ public function testGetAbsolutePath() $this->assertContains('_files/foo/bar', $dir->getAbsolutePath('bar')); } + public function testGetAbsolutePathOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->getAbsolutePath('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->getAbsolutePath('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->getAbsolutePath('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + public function testGetRelativePath() { $dir = $this->getDirectoryInstance('foo'); + $this->assertEquals( + 'file_three.txt', + $dir->getRelativePath('file_three.txt') + ); $this->assertEquals('', $dir->getRelativePath()); $this->assertEquals('bar', $dir->getRelativePath(__DIR__ . '/../_files/foo/bar')); } + public function testGetRelativePathOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->getRelativePath(__DIR__ .'/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->getRelativePath(__DIR__ .'//./..////Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->getRelativePath(__DIR__ .'\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->getRelativePath('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(4, $exceptions); + } + /** * Test for read method * @@ -72,6 +126,28 @@ public function readProvider() ]; } + public function testReadOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->read('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->read('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->read('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for search method * @@ -103,6 +179,28 @@ public function searchProvider() ]; } + public function testSearchOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->search('/*/*.txt', '../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->search('/*/*.txt', '//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->search('/*/*.txt', '\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for isExist method * @@ -127,6 +225,28 @@ public function existsProvider() return [['foo', 'bar', true], ['foo', 'bar/baz/', true], ['foo', 'bar/notexists', false]]; } + public function testIsExistOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->isExist('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isExist('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isExist('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for stat method * @@ -168,6 +288,28 @@ public function statProvider() return [['foo', 'bar'], ['foo', 'file_three.txt']]; } + public function testStatOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->stat('bar/../../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->stat('bar//./..///../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->stat('bar\..\..\..\Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for isReadable method * @@ -182,6 +324,28 @@ public function testIsReadable($dirPath, $path, $readable) $this->assertEquals($readable, $dir->isReadable($path)); } + public function testIsReadableOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->isReadable('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isReadable('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isReadable('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for isFile method * @@ -194,6 +358,28 @@ public function testIsFile($path, $isFile) $this->assertEquals($isFile, $this->getDirectoryInstance('foo')->isFile($path)); } + public function testIsFileOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->isFile('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isFile('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isFile('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for isDirectory method * @@ -206,6 +392,28 @@ public function testIsDirectory($path, $isDirectory) $this->assertEquals($isDirectory, $this->getDirectoryInstance('foo')->isDirectory($path)); } + public function testIsDirectoryOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->isDirectory('../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isDirectory('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isDirectory('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Data provider for testIsReadable * @@ -246,6 +454,28 @@ public function testOpenFile() $this->assertTrue($file instanceof \Magento\Framework\Filesystem\File\ReadInterface); } + public function testOpenFileOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->openFile('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->openFile('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->openFile('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test readFile * @@ -268,10 +498,35 @@ public function readFileProvider() { return [ ['popup.csv', 'var myData = 5;'], - ['data.csv', '"field1", "field2"' . "\n" . '"field3", "field4"' . "\n"] + [ + 'data.csv', + '"field1", "field2"' . PHP_EOL . '"field3", "field4"' . PHP_EOL + ] ]; } + public function testReadFileOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->readFile('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->readFile('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->readFile('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Get readable file instance * Get full path for files located in _files directory @@ -301,4 +556,26 @@ public function testReadRecursively() sort($expected); $this->assertEquals($expected, $actual); } + + public function testReadRecursivelyOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('foo'); + try { + $dir->readRecursively('../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->readRecursively('//./..///../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->readRecursively('\..\..\Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/WriteTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/WriteTest.php index 380c7998680a1..39a224ff902d5 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/WriteTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/WriteTest.php @@ -7,6 +7,7 @@ */ namespace Magento\Framework\Filesystem\Directory; +use Magento\Framework\Exception\ValidatorException; use Magento\Framework\Filesystem\DriverPool; use Magento\TestFramework\Helper\Bootstrap; @@ -63,6 +64,28 @@ public function createProvider() ]; } + public function testCreateOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->create('../../outsideDir'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->create('//./..///../outsideDir'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->create('\..\..\outsideDir'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for delete method * @@ -88,6 +111,28 @@ public function deleteProvider() return [['subdir'], ['subdir/subsubdir']]; } + public function testDeleteOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->delete('../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->delete('//./..///../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->delete('\..\..\Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for rename method (in scope of one directory instance) * @@ -119,6 +164,31 @@ public function renameProvider() return [['newDir1', 0777, 'first_name.txt', 'second_name.txt']]; } + public function testRenameOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->renameFile('../../Directory/ReadTest.php', 'RenamedTest'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->renameFile( + '//./..///../Directory/ReadTest.php', + 'RenamedTest' + ); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->renameFile('\..\..\Directory\ReadTest.php', 'RenamedTest'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for rename method (moving to new directory instance) * @@ -185,6 +255,40 @@ public function copyProvider() ]; } + public function testCopyOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + $dir->touch('test_file_for_copy_outside.txt'); + try { + $dir->copyFile('../../Directory/ReadTest.php', 'CopiedTest'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->copyFile( + '//./..///../Directory/ReadTest.php', + 'CopiedTest' + ); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->copyFile('\..\..\Directory\ReadTest.php', 'CopiedTest'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->copyFile( + 'test_file_for_copy_outside.txt', + '../../Directory/copied_outside.txt' + ); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(4, $exceptions); + } + /** * Test for copy method (copy to another directory instance) * @@ -231,6 +335,28 @@ public function testChangePermissions() $this->assertTrue($directory->changePermissions('test_directory', 0644)); } + public function testChangePermissionsOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->changePermissions('../../Directory', 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->changePermissions('//./..///../Directory', 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->changePermissions('\..\..\Directory', 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for changePermissionsRecursively method */ @@ -244,6 +370,28 @@ public function testChangePermissionsRecursively() $this->assertTrue($directory->changePermissionsRecursively('test_directory', 0777, 0644)); } + public function testChangePermissionsRecursivelyOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->changePermissionsRecursively('../foo', 0777, 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->changePermissionsRecursively('//./..///foo', 0777, 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->changePermissionsRecursively('\..\foo', 0777, 0777); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for touch method * @@ -274,6 +422,28 @@ public function touchProvider() ]; } + public function testTouchOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->touch('../../foo.tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->touch('//./..///../foo.tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->touch('\..\..\foo.tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test isWritable method */ @@ -285,6 +455,28 @@ public function testIsWritable() $this->assertTrue($directory->isWritable('bar')); } + public function testIsWritableOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->isWritable('../../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isWritable('//./..///../Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->isWritable('\..\..\Directory'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test for openFile method * @@ -315,6 +507,28 @@ public function openFileProvider() ]; } + public function testOpenFileOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->openFile('../../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->openFile('//./..///../Directory/ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->openFile('\..\..\Directory\ReadTest.php'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Test writeFile * @@ -359,6 +573,28 @@ public function writeFileProvider() return [['file1', '123', '456'], ['folder1/file1', '123', '456']]; } + public function testWriteFileOutside() + { + $exceptions = 0; + $dir = $this->getDirectoryInstance('newDir1', 0777); + try { + $dir->writeFile('../../Directory/ReadTest.php', 'tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->writeFile('//./..///../Directory/ReadTest.php', 'tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + try { + $dir->writeFile('\..\..\Directory\ReadTest.php', 'tst'); + } catch (ValidatorException $exception) { + $exceptions++; + } + $this->assertEquals(3, $exceptions); + } + /** * Tear down */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filter/TruncateFilterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filter/TruncateFilterTest.php new file mode 100644 index 0000000000000..3736012848379 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Filter/TruncateFilterTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Filter; + +class TruncateFilterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @param string $expectedValue + * @param string $expectedRemainder + * @param string $string + * @param int $length + * @param string $etc + * @param bool $breakWords + * @dataProvider truncateDataProvider + */ + public function testFilter( + $expectedValue, + $expectedRemainder, + $string, + $length = 5, + $etc = '...', + $breakWords = true + ) { + /** @var TruncateFilter $truncateFilter */ + $truncateFilter = \Magento\TestFramework\ObjectManager::getInstance()->create( + TruncateFilter::class, + [ + 'length' => $length, + 'etc' => $etc, + 'breakWords' => $breakWords, + ] + ); + $result = $truncateFilter->filter($string); + $this->assertEquals($expectedValue, $result->getValue()); + $this->assertEquals($expectedRemainder, $result->getRemainder()); + } + + public function truncateDataProvider() : array + { + return [ + '1' => [ + '12...', + '34567890', + '1234567890', + ], + '2' => [ + '123..', + ' 456 789', + '123 456 789', + 8, + '..', + false + ] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index d098e9bed4ffe..8583dcf3e4cd2 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -196,7 +196,7 @@ enumValues(includeDeprecated: true) { 'Missing type in the response' ); } - //Checks to make sure that the the given description exists in the expectedOutput array + //Checks to make sure that the given description exists in the expectedOutput array $this->assertTrue( array_key_exists( array_search( diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaA.graphqls b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaA.graphqls index d736bb4fa26f1..6c832e5f122a6 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaA.graphqls +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaA.graphqls @@ -2,6 +2,10 @@ type Query { placeholder: String @doc(description: "comment for placeholder.") } +type Mutation { + placeholder: String @doc(description: "comment for placeholder.") +} + input FilterTypeInput @doc(description:"Comment for FilterTypeInput") { eq: String @doc(description:"Equal") finset: [String] diff --git a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/ObjectManagerTest.php b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/ObjectManagerTest.php index 2f798deecd9d5..6d5da7243ffbe 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/ObjectManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/ObjectManagerTest.php @@ -7,6 +7,11 @@ class ObjectManagerTest extends \PHPUnit\Framework\TestCase { + /**#@+ + * Test class with type error + */ + const TEST_CLASS_WITH_TYPE_ERROR = \Magento\Framework\ObjectManager\TestAsset\ConstructorWithTypeError::class; + /**#@+ * Test classes for basic instantiation */ @@ -138,4 +143,17 @@ public function testNewInstance($actualClassName, array $properties = [], $expec } } } + + /** + * Test creating an object and passing incorrect type of arguments to the constructor. + * + * @expectedException \Magento\Framework\Exception\RuntimeException + * @expectedExceptionMessage Error occurred when creating object + */ + public function testNewInstanceWithTypeError() + { + self::$_objectManager->create(self::TEST_CLASS_WITH_TYPE_ERROR, [ + 'testArgument' => new \stdClass() + ]); + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/TestAsset/ConstructorWithTypeError.php b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/TestAsset/ConstructorWithTypeError.php new file mode 100644 index 0000000000000..92d16e4df3511 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/ObjectManager/TestAsset/ConstructorWithTypeError.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\ObjectManager\TestAsset; + +/** + * Test asset used to test invalid argument types on the constructor invocation. + */ +class ConstructorWithTypeError +{ + /** + * @var Basic + */ + private $testArgument; + + /** + * @param Basic $testArgument + */ + public function __construct(Basic $testArgument) + { + $this->testArgument = $testArgument; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/group.xml b/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/group.xml index 18501ecfbbfbf..8e50fe1888104 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/group.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/group.xml @@ -9,17 +9,17 @@ <block class="Magento\Framework\View\Element\Text" name="block1"> <block class="Magento\Framework\View\Element\Text" name="block2" group="group1"> <arguments> - <argument xsi:type="string" name="text">blok2</argument> + <argument xsi:type="string" name="text">block2</argument> </arguments> </block> <block class="Magento\Framework\View\Element\Text" name="block3" group="group1" > <arguments> - <argument xsi:type="string" name="text">blok3</argument> + <argument xsi:type="string" name="text">block3</argument> </arguments> </block> <block class="Magento\Framework\View\Element\Text" name="block4"> <arguments> - <argument xsi:type="string" name="text">blok4</argument> + <argument xsi:type="string" name="text">block4</argument> </arguments> </block> </block> diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php new file mode 100644 index 0000000000000..db4d9c5468640 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EntityAbstractTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ImportExport\Model\Import\Entity; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\ImportExport\Model\Import\Source\Csv; +use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; + +/** + * Test class for \Magento\ImportExport\Model\Import\AbstractEntity + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class EntityAbstractTest extends \PHPUnit\Framework\TestCase +{ + /** + * Test for method _saveValidatedBunches() + * + * @return void + */ + public function testSaveValidatedBunches() : void + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $filesystem = $objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = new Csv(__DIR__ . '/_files/advanced_price_for_validation_test.csv', $directory); + $source->rewind(); + + $eavConfig = $this->createMock(\Magento\Eav\Model\Config::class); + $entityTypeMock = $this->createMock(\Magento\Eav\Model\Entity\Type::class); + $eavConfig->expects($this->any())->method('getEntityType')->willReturn($entityTypeMock); + + /** @var $model AbstractEntity|\PHPUnit_Framework_MockObject_MockObject */ + $model = $this->getMockForAbstractClass( + AbstractEntity::class, + [ + $objectManager->get(\Magento\Framework\Json\Helper\Data::class), + $objectManager->get(\Magento\ImportExport\Helper\Data::class), + $objectManager->get(\Magento\ImportExport\Model\ResourceModel\Import\Data::class), + $eavConfig, + $objectManager->get(\Magento\Framework\App\ResourceConnection::class), + $objectManager->get(\Magento\ImportExport\Model\ResourceModel\Helper::class), + $objectManager->get(\Magento\Framework\Stdlib\StringUtils::class), + $objectManager->get(ProcessingErrorAggregatorInterface::class), + ], + '', + true, + false, + true, + ['validateRow', 'getEntityTypeCode'] + ); + $model->expects($this->any())->method('validateRow')->willReturn(true); + $model->expects($this->any())->method('getEntityTypeCode')->willReturn('catalog_product'); + + $model->setSource($source); + + $method = new \ReflectionMethod($model, '_saveValidatedBunches'); + $method->setAccessible(true); + $method->invoke($model); + + $this->assertEquals(1, $model->getProcessedEntitiesCount()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv new file mode 100644 index 0000000000000..fb42afb901880 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/_files/advanced_price_for_validation_test.csv @@ -0,0 +1,3 @@ +sku,tier_price_website,tier_price_customer_group,tier_price_qty,tier_price,tier_price_value_type +SimpleProduct,All Websites [USD],ALL GROUPS,100,50,Fixed +SimpleProduct,All Websites [USD],ALL GROUPS,33,55,Fixed diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/_files/product.php b/dev/tests/integration/testsuite/Magento/ImportExport/_files/product.php index b1a239ccd2246..102d1d1268afd 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/_files/product.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/_files/product.php @@ -23,7 +23,7 @@ \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED )->setWebsiteIds( [1] -)->setCateroryIds( +)->setCategoryIds( [] )->setStockData( ['qty' => 100, 'is_in_stock' => 1, 'manage_stock' => 1] diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index f362e75ea790e..4acba63d98e66 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -130,7 +130,7 @@ public function testCheckThatPidFilesWasCreated() public function testSpecificConsumerAndRerun() { $specificConsumer = 'quoteItemCleaner'; - $pidFilePath = $specificConsumer . ConsumersRunner::PID_FILE_EXT; + $pidFilePath = $this->getPidFileName($specificConsumer); $config = $this->config; $config['cron_consumers_runner'] = ['consumers' => [$specificConsumer], 'max_messages' => 0]; @@ -228,7 +228,7 @@ private function writeConfig(array $config) private function getPidFileFullPath($consumerName) { $directoryList = $this->objectManager->get(DirectoryList::class); - return $directoryList->getPath(DirectoryList::VAR_DIR) . '/' . $consumerName . ConsumersRunner::PID_FILE_EXT; + return $directoryList->getPath(DirectoryList::VAR_DIR) . '/' . $this->getPidFileName($consumerName); } /** @@ -239,7 +239,7 @@ protected function tearDown() foreach ($this->consumerConfig->getConsumers() as $consumer) { $consumerName = $consumer->getName(); $pidFileFullPath = $this->getPidFileFullPath($consumerName); - $pidFilePath = $consumerName . ConsumersRunner::PID_FILE_EXT; + $pidFilePath = $this->getPidFileName($consumerName); $pid = $this->pid->getPid($pidFilePath); if ($pid && $this->pid->isRun($pidFilePath)) { @@ -258,4 +258,15 @@ protected function tearDown() $this->writeConfig($this->config); $this->appConfig->reinit(); } + + /** + * @param string $consumerName The consumers name + * @return string The name to file with PID + */ + private function getPidFileName($consumerName) + { + $sanitizedHostname = preg_replace('/[^a-z0-9]/i', '', gethostname()); + + return $consumerName . '-' . $sanitizedHostname . ConsumersRunner::PID_FILE_EXT; + } } diff --git a/dev/tests/integration/testsuite/Magento/Multishipping/Controller/Checkout/CheckItemsTest.php b/dev/tests/integration/testsuite/Magento/Multishipping/Controller/Checkout/CheckItemsTest.php new file mode 100644 index 0000000000000..02f031fae336a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Multishipping/Controller/Checkout/CheckItemsTest.php @@ -0,0 +1,176 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Multishipping\Controller\Checkout; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Checkout\Model\Session; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\Exception\LocalizedException; +use Magento\Quote\Model\Quote; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\CartRepositoryInterface as QuoteRepository; +use Magento\Framework\Serialize\Serializer\Json; + +/** + * Test class for \Magento\Multishipping\Controller\Checkout + * + * @magentoAppArea frontend + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/Customer/_files/customer.php + */ +class CheckItemsTest extends \Magento\TestFramework\TestCase\AbstractController +{ + /** + * @var Quote + */ + private $quote; + + /** + * @var Session + */ + private $checkoutSession; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Json + */ + private $json; + + /** + * @inheritdoc + */ + public function setUp() + { + parent::setUp(); + + $this->checkoutSession = $this->_objectManager->get(Session::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->json = $this->_objectManager->get(Json::class); + $this->quote = $this->getQuote('test01'); + $this->checkoutSession->setQuoteId($this->quote->getId()); + $this->checkoutSession->setCartWasUpdated(false); + } + + /** + * Validator of quote items. + * + * @param array $requestQuantity + * @param array $expectedResponse + * + * @magentoConfigFixture current_store multishipping/options/checkout_multiple 1 + * @magentoConfigFixture current_store multishipping/options/checkout_multiple_maximum_qty 200 + * @dataProvider requestDataProvider + */ + public function testExecute($requestQuantity, $expectedResponse) + { + $this->loginCustomer(); + + try { + /** @var $product Product */ + $product = $this->productRepository->get('simple'); + } catch (\Exception $e) { + $this->fail('No such product entity'); + } + + $quoteItem = $this->quote->getItemByProduct($product); + $this->assertNotFalse($quoteItem, 'Cannot get quote item for simple product'); + + $request = []; + if (!empty($requestQuantity) && is_array($requestQuantity)) { + $request= [ + 'ship' => [ + [$quoteItem->getId() => $requestQuantity], + ] + ]; + } + + $this->getRequest()->setPostValue($request); + $this->dispatch('multishipping/checkout/checkItems'); + $response = $this->getResponse()->getBody(); + + $this->assertEquals($expectedResponse, $this->json->unserialize($response)); + } + + /** + * Authenticates customer and creates customer session. + */ + private function loginCustomer() + { + $logger = $this->createMock(\Psr\Log\LoggerInterface::class); + /** @var AccountManagementInterface $service */ + $service = $this->_objectManager->create(AccountManagementInterface::class); + try { + $customer = $service->authenticate('customer@example.com', 'password'); + } catch (LocalizedException $e) { + $this->fail($e->getMessage()); + } + /** @var CustomerSession $customerSession */ + $customerSession = $this->_objectManager->create(CustomerSession::class, [$logger]); + $customerSession->setCustomerDataAsLoggedIn($customer); + } + + /** + * Gets quote by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuote($reservedOrderId) + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->create(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + /** @var QuoteRepository $quoteRepository */ + $quoteRepository = $this->_objectManager->get(QuoteRepository::class); + $items = $quoteRepository->getList($searchCriteria) + ->getItems(); + return array_pop($items); + } + + /** + * Variations of request data. + * @returns array + */ + public function requestDataProvider(): array + { + return [ + [ + 'request' => [], + 'response' => [ + 'success' => false, + 'error_message' => 'We are unable to process your request. Please, try again later.' + ] + ], + [ + 'request' => ['qty' => 2], + 'response' => [ + 'success' => true, + ] + ], + [ + 'request' => ['qty' => 101], + 'response' => [ + 'success' => false, + 'error_message' => 'The requested qty is not available'] + ], + [ + 'request' => ['qty' => 230], + 'response' => [ + 'success' => false, + 'error_message' => 'Maximum qty allowed for Shipping to multiple addresses is 200'] + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php index 1a27994b607f1..5a094eb05c774 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/ManageTest.php @@ -21,6 +21,9 @@ class ManageTest extends \Magento\TestFramework\TestCase\AbstractController */ protected $coreSession; + /** + * Test setup + */ protected function setUp() { parent::setUp(); @@ -31,6 +34,9 @@ protected function setUp() $this->coreSession->setData('_form_key', 'formKey'); } + /** + * test tearDown + */ protected function tearDown() { $this->customerSession->setCustomerId(null); @@ -58,7 +64,7 @@ public function testSaveAction() * Check that success message */ $this->assertSessionMessages( - $this->equalTo(['We saved the subscription.']), + $this->equalTo(['We have saved your subscription.']), \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS ); } @@ -68,6 +74,7 @@ public function testSaveAction() */ public function testSaveActionRemoveSubscription() { + $this->getRequest() ->setParam('form_key', 'formKey') ->setParam('is_subscribed', '0'); @@ -84,7 +91,7 @@ public function testSaveActionRemoveSubscription() * Check that success message */ $this->assertSessionMessages( - $this->equalTo(['We removed the subscription.']), + $this->equalTo(['We have updated your subscription.']), \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS ); } diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php index 39db400d2d637..ab38dcf158c56 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php @@ -63,14 +63,14 @@ public function testCustomerCreated() ->setFirstname('Firstname') ->setLastname('Lastname') ->setEmail('customer_two@example.com'); - $createdCustomer = $this->customerRepository->save( + $this->customerRepository->save( $customerDataObject, $this->accountManagement->getPasswordHash('password') ); $subscriber->loadByEmail('customer_two@example.com'); $this->assertTrue($subscriber->isSubscribed()); - $this->assertEquals((int)$createdCustomer->getId(), (int)$subscriber->getCustomerId()); + $this->assertEquals(0, (int)$subscriber->getCustomerId()); } /** diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/SubscriberTest.php index 356cedde57772..b6f9a22ff5273 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/ResourceModel/SubscriberTest.php @@ -40,15 +40,13 @@ public function testLoadByCustomerDataWithCustomerId() * @magentoDataFixture Magento/Newsletter/_files/subscribers.php * @magentoDataFixture Magento/Customer/_files/two_customers.php */ - public function testLoadByCustomerDataWithoutCustomerId() + public function testTryLoadByCustomerDataWithoutCustomerId() { /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ $customerRepository = Bootstrap::getObjectManager() ->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); $customerData = $customerRepository->getById(2); $result = $this->_resourceModel->loadByCustomerData($customerData); - - $this->assertEquals(0, $result['customer_id']); - $this->assertEquals('customer_two@example.com', $result['subscriber_email']); + $this->assertEmpty($result); } } diff --git a/dev/tests/integration/testsuite/Magento/PageCache/Plugin/RegisterFormKeyFromCookieTest.php b/dev/tests/integration/testsuite/Magento/PageCache/Plugin/RegisterFormKeyFromCookieTest.php new file mode 100644 index 0000000000000..eb23b0b185a00 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/PageCache/Plugin/RegisterFormKeyFromCookieTest.php @@ -0,0 +1,68 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Plugin; + +use Magento\Framework\App\FrontController; +use Magento\Framework\App\RequestInterface; +use PHPUnit\Framework\TestCase; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; +use Magento\TestFramework\Helper\Bootstrap; + +class RegisterFormKeyFromCookieTest extends TestCase +{ + /** + * @var HttpRequest + */ + private $request; + + /** + * @var FrontController + */ + private $frontController; + + /** + * @var FormKeyValidator + */ + private $formKeyValidator; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->request = $objectManager->get(RequestInterface::class); + $this->frontController = $objectManager->get( + FrontController::class + ); + $this->formKeyValidator = $objectManager->get(FormKeyValidator::class); + } + + /** + * @magentoAppArea frontend + */ + public function testTakenFromCookie() + { + if (!Bootstrap::canTestHeaders()) { + $this->markTestSkipped( + 'Can\'t test dispatch process without sending headers' + ); + } + $_SERVER['HTTP_HOST'] = 'localhost'; + $formKey = 'customFormKey'; + $_COOKIE['form_key'] = $formKey; + $this->request->setMethod(HttpRequest::METHOD_POST); + $this->request->setParam('form_key', $formKey); + $this->request->setRequestUri('core/index/index'); + $this->frontController->dispatch($this->request); + $this->assertTrue($this->formKeyValidator->validate($this->request)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml index 671ab6e4b5619..2bd346a6e8f7b 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml @@ -350,7 +350,7 @@ <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41"> <comment><![CDATA[Payflow Link lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> @@ -371,12 +371,12 @@ <group id="payflow_link_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="60"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> @@ -731,7 +731,7 @@ <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/paypal_express_bml/active</config_path> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -753,12 +753,12 @@ <group id="advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="30"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="10"> <label>Publisher ID</label> @@ -767,9 +767,8 @@ <attribute type="shared">1</attribute> </field> <field id="bml_wizard" translate="button_label" sortOrder="15" showInDefault="1" showInWebsite="1"> - <label></label> <button_label>Get Publisher ID from PayPal</button_label> - <button_url><![CDATA[https:/financing.paypal.com/ppfinportal/cart/index?dcp=4eff8563b9cc505e0b9afaff3256705081553c79]]></button_url> + <button_url><![CDATA[https://financing.paypal.com/ppfinportal/cart/index?dcp=4eff8563b9cc505e0b9afaff3256705081553c79]]></button_url> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\BmlApiWizard</frontend_model> </field> <group id="settings_bml_homepage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> @@ -1262,7 +1261,7 @@ <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21"> <comment><![CDATA[Payments Pro Hosted Solution lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <requires> <field id="pphs_enable"/> @@ -1271,12 +1270,12 @@ <group id="pphs_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="22"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> @@ -1537,7 +1536,7 @@ <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml"> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> @@ -1837,7 +1836,7 @@ <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https:/www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> + <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> </comment> <config_path>payment/payflow_express_bml/active</config_path> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> @@ -1856,12 +1855,12 @@ <group id="paypal_payflow_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="40"> <label>Advertise PayPal Credit</label> <comment> - <![CDATA[<a href="https:/financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> + <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https:/financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> + or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> </comment> <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php index 2318edbb80b25..04fb36312f300 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php @@ -22,6 +22,8 @@ /** * @magentoAppIsolation enabled + * @magentoDbIsolation disabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class SecureTokenTest extends TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Plugin/UpdateQuoteStoreTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Plugin/UpdateQuoteStoreTest.php new file mode 100644 index 0000000000000..cbacab1139c10 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Plugin/UpdateQuoteStoreTest.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\Plugin; + +use Magento\Checkout\Model\Session; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\FunctionalTestingFramework\ObjectManagerInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\StoreCookieManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\StoreRepository; +use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper; + +/** + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UpdateQuoteStoreTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + + protected function setUp() + { + $this->objectManager = BootstrapHelper::getObjectManager(); + $this->quoteRepository = $this->objectManager->create(CartRepositoryInterface::class); + } + + /** + * Tests that active quote store id updates after store cookie change. + * + * @magentoDataFixture Magento/Quote/_files/empty_quote.php + * @magentoDataFixture Magento/Store/_files/second_store.php + * @throws \ReflectionException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testUpdateQuoteStoreAfterChangeStoreCookie() + { + $secondStoreCode = 'fixture_second_store'; + $reservedOrderId = 'reserved_order_id'; + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $currentStore = $storeManager->getStore(); + + $quote = $this->getQuote($reservedOrderId); + $this->assertEquals( + $currentStore->getId(), + $quote->getStoreId(), + 'Current store id and quote store id are not match' + ); + + /** @var Session $checkoutSession */ + $checkoutSession = $this->objectManager->get(Session::class); + $checkoutSession->setQuoteId($quote->getId()); + + $storeRepository = $this->objectManager->create(StoreRepository::class); + $secondStore = $storeRepository->get($secondStoreCode); + + $storeCookieManager = $this->getStoreCookieManager($currentStore); + $storeCookieManager->setStoreCookie($secondStore); + + $updatedQuote = $this->getQuote($reservedOrderId); + $this->assertEquals( + $secondStore->getId(), + $updatedQuote->getStoreId(), + 'Active quote store id should be equal second store id' + ); + } + + /** + * Retrieves quote by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuote(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + $items = $this->quoteRepository->getList($searchCriteria)->getItems(); + + return array_pop($items); + } + + /** + * Returns instance of StoreCookieManagerInterface with mocked cookieManager dependency. + * + * Mock is needed since integration test framework use own cookie manager with + * behavior that differs from real environment. + * + * @param $currentStore + * @return StoreCookieManagerInterface + * @throws \ReflectionException + */ + private function getStoreCookieManager(StoreInterface $currentStore): StoreCookieManagerInterface + { + /** @var StoreCookieManagerInterface $storeCookieManager */ + $storeCookieManager = $this->objectManager->get(StoreCookieManagerInterface::class); + $cookieManagerMock = $this->getMockBuilder(\Magento\Framework\Stdlib\Cookie\PhpCookieManager::class) + ->disableOriginalConstructor() + ->getMock(); + $cookieManagerMock->method('getCookie') + ->willReturn($currentStore->getCode()); + + $reflection = new \ReflectionClass($storeCookieManager); + $cookieManager = $reflection->getProperty('cookieManager'); + $cookieManager->setAccessible(true); + $cookieManager->setValue($storeCookieManager, $cookieManagerMock); + + return $storeCookieManager; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php index d59c402e22a71..62e417d27c51b 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php @@ -11,7 +11,7 @@ * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php */ -class AddressTest extends \PHPUnit\Framework\TestCase +class AddressTest extends \Magento\TestFramework\Indexer\TestCase { /** @var \Magento\Quote\Model\Quote $quote */ protected $_quote; @@ -25,6 +25,19 @@ class AddressTest extends \PHPUnit\Framework\TestCase /**@var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ protected $customerRepository; + public static function setUpBeforeClass() + { + $db = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + /** * Initialize quote and customer fixtures */ diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteRepositoryTest.php index 88c66fedd10ed..397591918129c 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteRepositoryTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Quote\Model; +use Magento\Store\Model\StoreRepository; use Magento\TestFramework\Helper\Bootstrap as BootstrapHelper; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Api\SearchCriteriaBuilder; @@ -19,6 +20,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @magentoDbIsolation disabled */ class QuoteRepositoryTest extends \PHPUnit\Framework\TestCase { @@ -42,6 +44,9 @@ class QuoteRepositoryTest extends \PHPUnit\Framework\TestCase */ private $filterBuilder; + /** + * Set up + */ protected function setUp() { $this->objectManager = BootstrapHelper::getObjectManager(); @@ -50,6 +55,34 @@ protected function setUp() $this->filterBuilder = $this->objectManager->create(FilterBuilder::class); } + /** + * Tests that quote saved with custom store id has same store id after getting via repository. + * + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/Store/_files/second_store.php + */ + public function testGetQuoteWithCustomStoreId() + { + $secondStoreCode = 'fixture_second_store'; + $reservedOrderId = 'test01'; + + $storeRepository = $this->objectManager->create(StoreRepository::class); + $secondStore = $storeRepository->get($secondStoreCode); + + // Set store_id in quote to second store_id + $quote = $this->getQuote($reservedOrderId); + $quote->setStoreId($secondStore->getId()); + $this->quoteRepository->save($quote); + + $savedQuote = $this->quoteRepository->get($quote->getId()); + + $this->assertEquals( + $secondStore->getId(), + $savedQuote->getStoreId(), + 'Quote store id should be equal with store id value in DB' + ); + } + /** * @magentoDataFixture Magento/Sales/_files/quote.php */ @@ -75,7 +108,6 @@ public function testGetListDoubleCall() } /** - * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ public function testSaveWithNotExistingCustomerAddress() @@ -126,6 +158,24 @@ public function testSaveWithNotExistingCustomerAddress() ); } + /** + * Returns quote by reserved order id. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote(string $reservedOrderId) + { + $searchCriteria = $this->getSearchCriteria($reservedOrderId); + $searchResult = $this->quoteRepository->getList($searchCriteria); + $items = $searchResult->getItems(); + + /** @var CartInterface $quote */ + $quote = array_pop($items); + + return $quote; + } + /** * Get search criteria * diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php index 39db5b1572cb7..6ea25c8f337df 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php @@ -3,21 +3,48 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Quote\Model; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ProductRepository; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Customer\Model\Data\Customer; use Magento\Framework\Exception\LocalizedException; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\Api\ExtensibleDataInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class QuoteTest extends \PHPUnit\Framework\TestCase { - private function convertToArray($entity) + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + } + + /** + * @param ExtensibleDataInterface $entity + * @return array + */ + private function convertToArray(ExtensibleDataInterface $entity): array { - return Bootstrap::getObjectManager() + return $this->objectManager ->create(\Magento\Framework\Api\ExtensibleDataObjectConverter::class) ->toFlatArray($entity); } @@ -25,14 +52,15 @@ private function convertToArray($entity) /** * @magentoDataFixture Magento/Catalog/_files/product_virtual.php * @magentoDataFixture Magento/Sales/_files/quote.php + * @return void */ - public function testCollectTotalsWithVirtual() + public function testCollectTotalsWithVirtual(): void { - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); - $productRepository = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Api\ProductRepositoryInterface::class + $productRepository = $this->objectManager->create( + ProductRepositoryInterface::class ); $product = $productRepository->get('virtual-product', false, null, true); $quote->addProduct($product); @@ -44,16 +72,19 @@ public function testCollectTotalsWithVirtual() $this->assertEquals(20, $quote->getBaseGrandTotal()); } - public function testSetCustomerData() + /** + * @return void + */ + public function testSetCustomerData(): void { - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); /** @var CustomerInterfaceFactory $customerFactory */ - $customerFactory = Bootstrap::getObjectManager()->create( + $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); /** @var \Magento\Framework\Api\DataObjectHelper $dataObjectHelper */ - $dataObjectHelper = Bootstrap::getObjectManager()->create(\Magento\Framework\Api\DataObjectHelper::class); + $dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); $expected = $this->_getCustomerDataArray(); $customer = $customerFactory->create(); $dataObjectHelper->populateWithArray( @@ -64,21 +95,23 @@ public function testSetCustomerData() $this->assertEquals($expected, $this->convertToArray($customer)); $quote->setCustomer($customer); - // $customer = $quote->getCustomer(); $this->assertEquals($expected, $this->convertToArray($customer)); $this->assertEquals('qa@example.com', $quote->getCustomerEmail()); } - public function testUpdateCustomerData() + /** + * @return void + */ + public function testUpdateCustomerData(): void { - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); - $customerFactory = Bootstrap::getObjectManager()->create( + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); + $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); /** @var \Magento\Framework\Api\DataObjectHelper $dataObjectHelper */ - $dataObjectHelper = Bootstrap::getObjectManager()->create(\Magento\Framework\Api\DataObjectHelper::class); + $dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); $expected = $this->_getCustomerDataArray(); //For save in repository $expected = $this->removeIdFromCustomerData($expected); @@ -92,7 +125,7 @@ public function testUpdateCustomerData() /** * @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = Bootstrap::getObjectManager() + $customerRepository = $this->objectManager ->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); $customerRepository->save($customerDataSet); $quote->setCustomer($customerDataSet); @@ -116,18 +149,20 @@ public function testUpdateCustomerData() /** * Customer data is set to quote (which contains valid group ID). + * + * @return void */ - public function testGetCustomerGroupFromCustomer() + public function testGetCustomerGroupFromCustomer(): void { /** Preconditions */ /** @var CustomerInterfaceFactory $customerFactory */ - $customerFactory = Bootstrap::getObjectManager()->create( + $customerFactory = $this->objectManager->create( CustomerInterfaceFactory::class ); $customerGroupId = 3; $customerData = $customerFactory->create()->setId(1)->setGroupId($customerGroupId); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $quote->setCustomer($customerData); $quote->unsetData('customer_group_id'); @@ -137,8 +172,9 @@ public function testGetCustomerGroupFromCustomer() /** * @magentoDataFixture Magento/Customer/_files/customer_group.php + * @return void */ - public function testGetCustomerTaxClassId() + public function testGetCustomerTaxClassId(): void { /** * Preconditions: create quote and assign ID of customer group created in fixture to it. @@ -146,10 +182,10 @@ public function testGetCustomerTaxClassId() $fixtureGroupCode = 'custom_group'; $fixtureTaxClassId = 3; /** @var \Magento\Customer\Model\Group $group */ - $group = Bootstrap::getObjectManager()->create(\Magento\Customer\Model\Group::class); + $group = $this->objectManager->create(\Magento\Customer\Model\Group::class); $fixtureGroupId = $group->load($fixtureGroupCode, 'customer_group_code')->getId(); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $quote->setCustomerGroupId($fixtureGroupId); /** Execute SUT */ @@ -162,15 +198,16 @@ public function testGetCustomerTaxClassId() * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @return void */ - public function testAssignCustomerWithAddressChangeAddressesNotSpecified() + public function testAssignCustomerWithAddressChangeAddressesNotSpecified(): void { /** Preconditions: * Customer with two addresses created * First address is default billing, second is default shipping. */ - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $customerData = $this->_prepareQuoteForTestAssignCustomerWithAddressChange($quote); /** Execute SUT */ @@ -227,16 +264,16 @@ public function testAssignCustomerWithAddressChangeAddressesNotSpecified() * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @return void */ - public function testAssignCustomerWithAddressChange() + public function testAssignCustomerWithAddressChange(): void { /** Preconditions: * Customer with two addresses created * First address is default billing, second is default shipping. */ - /** @var \Magento\Quote\Model\Quote $quote */ - $objectManager = Bootstrap::getObjectManager(); - $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $customerData = $this->_prepareQuoteForTestAssignCustomerWithAddressChange($quote); /** @var \Magento\Quote\Model\Quote\Address $quoteBillingAddress */ $expectedBillingAddressData = [ @@ -249,7 +286,7 @@ public function testAssignCustomerWithAddressChange() 'firstname' => 'FirstBilling', 'region_id' => 1 ]; - $quoteBillingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class); + $quoteBillingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteBillingAddress->setData($expectedBillingAddressData); $expectedShippingAddressData = [ @@ -262,7 +299,7 @@ public function testAssignCustomerWithAddressChange() 'firstname' => 'FirstShipping', 'region_id' => 1 ]; - $quoteShippingAddress = $objectManager->create(\Magento\Quote\Model\Quote\Address::class); + $quoteShippingAddress = $this->objectManager->create(\Magento\Quote\Model\Quote\Address::class); $quoteShippingAddress->setData($expectedShippingAddressData); /** Execute SUT */ @@ -292,17 +329,18 @@ public function testAssignCustomerWithAddressChange() /** * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php + * @return void */ - public function testAddProductUpdateItem() + public function testAddProductUpdateItem(): void { - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); $productStockQty = 100; - $productRepository = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Api\ProductRepositoryInterface::class + $productRepository = $this->objectManager->create( + ProductRepositoryInterface::class ); $product = $productRepository->get('simple-1', false, null, true); @@ -324,7 +362,7 @@ public function testAddProductUpdateItem() $this->assertEquals(1, $quote->getItemsQty()); $this->expectException(\Magento\Framework\Exception\LocalizedException::class); - $this->expectExceptionMessage('We don\'t have as many "Simple Product" as you requested.'); + $this->expectExceptionMessage('The requested qty is not available'); $updateParams['qty'] = $productStockQty + 1; $quote->updateItem($updateParams['id'], $updateParams); } @@ -334,17 +372,17 @@ public function testAddProductUpdateItem() * * Customer with two addresses created. First address is default billing, second is default shipping. * - * @param \Magento\Quote\Model\Quote $quote - * @return \Magento\Customer\Api\Data\CustomerInterface + * @param Quote $quote + * @return CustomerInterface */ - protected function _prepareQuoteForTestAssignCustomerWithAddressChange($quote) + protected function _prepareQuoteForTestAssignCustomerWithAddressChange(Quote $quote): CustomerInterface { - $objectManager = Bootstrap::getObjectManager(); - /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $customerRepository = $this->objectManager->create( + CustomerRepositoryInterface::class + ); $fixtureCustomerId = 1; /** @var \Magento\Customer\Model\Customer $customer */ - $customer = $objectManager->create(\Magento\Customer\Model\Customer::class); + $customer = $this->objectManager->create(\Magento\Customer\Model\Customer::class); $fixtureSecondAddressId = 2; $customer->load($fixtureCustomerId)->setDefaultShipping($fixtureSecondAddressId)->save(); $customerData = $customerRepository->getById($fixtureCustomerId); @@ -360,11 +398,11 @@ protected function _prepareQuoteForTestAssignCustomerWithAddressChange($quote) } /** - * @param $email + * @param string $email * @param array $customerData * @return array */ - protected function changeEmailInCustomerData($email, array $customerData) + protected function changeEmailInCustomerData(string $email, array $customerData): array { $customerData[\Magento\Customer\Model\Data\Customer::EMAIL] = $email; return $customerData; @@ -374,34 +412,36 @@ protected function changeEmailInCustomerData($email, array $customerData) * @param array $customerData * @return array */ - protected function removeIdFromCustomerData(array $customerData) + protected function removeIdFromCustomerData(array $customerData): array { unset($customerData[\Magento\Customer\Model\Data\Customer::ID]); return $customerData; } - protected function _getCustomerDataArray() + /** + * @return array + */ + protected function _getCustomerDataArray(): array { return [ - \Magento\Customer\Model\Data\Customer::CONFIRMATION => 'test', - \Magento\Customer\Model\Data\Customer::CREATED_AT => '2/3/2014', - \Magento\Customer\Model\Data\Customer::CREATED_IN => 'Default', - \Magento\Customer\Model\Data\Customer::DEFAULT_BILLING => 'test', - \Magento\Customer\Model\Data\Customer::DEFAULT_SHIPPING => 'test', - \Magento\Customer\Model\Data\Customer::DOB => '2014-02-03 00:00:00', - \Magento\Customer\Model\Data\Customer::EMAIL => 'qa@example.com', - \Magento\Customer\Model\Data\Customer::FIRSTNAME => 'Joe', - \Magento\Customer\Model\Data\Customer::GENDER => 0, - \Magento\Customer\Model\Data\Customer::GROUP_ID => - \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID, - \Magento\Customer\Model\Data\Customer::ID => 1, - \Magento\Customer\Model\Data\Customer::LASTNAME => 'Dou', - \Magento\Customer\Model\Data\Customer::MIDDLENAME => 'Ivan', - \Magento\Customer\Model\Data\Customer::PREFIX => 'Dr.', - \Magento\Customer\Model\Data\Customer::STORE_ID => 1, - \Magento\Customer\Model\Data\Customer::SUFFIX => 'Jr.', - \Magento\Customer\Model\Data\Customer::TAXVAT => 1, - \Magento\Customer\Model\Data\Customer::WEBSITE_ID => 1 + Customer::CONFIRMATION => 'test', + Customer::CREATED_AT => '2/3/2014', + Customer::CREATED_IN => 'Default', + Customer::DEFAULT_BILLING => 'test', + Customer::DEFAULT_SHIPPING => 'test', + Customer::DOB => '2014-02-03 00:00:00', + Customer::EMAIL => 'qa@example.com', + Customer::FIRSTNAME => 'Joe', + Customer::GENDER => 0, + Customer::GROUP_ID => \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID, + Customer::ID => 1, + Customer::LASTNAME => 'Dou', + Customer::MIDDLENAME => 'Ivan', + Customer::PREFIX => 'Dr.', + Customer::STORE_ID => 1, + Customer::SUFFIX => 'Jr.', + Customer::TAXVAT => 1, + Customer::WEBSITE_ID => 1 ]; } @@ -410,12 +450,12 @@ protected function _getCustomerDataArray() * * @magentoDataFixture Magento/Sales/_files/order.php * @magentoDataFixture Magento/Quote/_files/empty_quote.php + * @return void */ - public function testReserveOrderId() + public function testReserveOrderId(): void { - $objectManager = Bootstrap::getObjectManager(); - /** @var \Magento\Quote\Model\Quote $quote */ - $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); $quote->load('reserved_order_id', 'reserved_order_id'); $quote->reserveOrderId(); $this->assertEquals('reserved_order_id', $quote->getReservedOrderId()); @@ -427,38 +467,38 @@ public function testReserveOrderId() /** * Test to verify that disabled product cannot be added to cart * @magentoDataFixture Magento/Quote/_files/is_not_salable_product.php + * @return void */ - public function testAddedProductToQuoteIsSalable() + public function testAddedProductToQuoteIsSalable(): void { $productId = 99; - $objectManager = Bootstrap::getObjectManager(); /** @var ProductRepository $productRepository */ - $productRepository = $objectManager->get(ProductRepository::class); + $productRepository = $this->objectManager->get(ProductRepository::class); - /** @var \Magento\Quote\Model\Quote $quote */ + /** @var Quote $quote */ $product = $productRepository->getById($productId, false, null, true); $this->expectException(LocalizedException::class); $this->expectExceptionMessage('Product that you are trying to add is not available.'); - $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); + $quote = $this->objectManager->create(Quote::class); $quote->addProduct($product); } /** * @magentoDataFixture Magento/Sales/_files/quote.php * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @return void */ - public function testGetItemById() + public function testGetItemById(): void { - $objectManager = Bootstrap::getObjectManager(); - $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); + $quote = $this->objectManager->create(Quote::class); $quote->load('test01', 'reserved_order_id'); - $quoteItem = $objectManager->create(\Magento\Quote\Model\Quote\Item::class); + $quoteItem = $this->objectManager->create(\Magento\Quote\Model\Quote\Item::class); - $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); $product = $productRepository->get('simple'); $quoteItem->setProduct($product); @@ -468,4 +508,101 @@ public function testGetItemById() $this->assertInstanceOf(\Magento\Quote\Model\Quote\Item::class, $quote->getItemById($quoteItem->getId())); $this->assertEquals($quoteItem->getId(), $quote->getItemById($quoteItem->getId())->getId()); } + + /** + * Tests of quotes merging. + * + * @param int|null $guestItemGiftMessageId + * @param int|null $customerItemGiftMessageId + * @param int|null $guestOrderGiftMessageId + * @param int|null $customerOrderGiftMessageId + * @param int|null $expectedItemGiftMessageId + * @param int|null $expectedOrderGiftMessageId + * + * @magentoDataFixture Magento/Sales/_files/quote.php + * @dataProvider giftMessageDataProvider + * @throws LocalizedException + * @return void + */ + public function testMerge( + $guestItemGiftMessageId, + $customerItemGiftMessageId, + $guestOrderGiftMessageId, + $customerOrderGiftMessageId, + $expectedItemGiftMessageId, + $expectedOrderGiftMessageId + ): void { + $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $product = $productRepository->get('simple', false, null, true); + + /** @var Quote $quote */ + $guestQuote = $this->getQuote('test01'); + $guestQuote->setGiftMessageId($guestOrderGiftMessageId); + + /** @var Quote $customerQuote */ + $customerQuote = $this->objectManager->create(Quote::class); + $customerQuote->setReservedOrderId('test02') + ->setStoreId($guestQuote->getStoreId()) + ->addProduct($product); + $customerQuote->setGiftMessageId($customerOrderGiftMessageId); + + $guestItem = $guestQuote->getItemByProduct($product); + $guestItem->setGiftMessageId($guestItemGiftMessageId); + + $customerItem = $customerQuote->getItemByProduct($product); + $customerItem->setGiftMessageId($customerItemGiftMessageId); + + $customerQuote->merge($guestQuote); + $mergedItemItem = $customerQuote->getItemByProduct($product); + + self::assertEquals($expectedOrderGiftMessageId, $customerQuote->getGiftMessageId()); + self::assertEquals($expectedItemGiftMessageId, $mergedItemItem->getGiftMessageId()); + } + + /** + * Provides order- and item-level gift message Id. + * + * @return array + */ + public function giftMessageDataProvider(): array + { + return [ + [ + 'guestItemId' => null, + 'customerItemId' => 1, + 'guestOrderId' => null, + 'customerOrderId' => 11, + 'expectedItemId' => 1, + 'expectedOrderId' => 11 + ], + [ + 'guestItemId' => 1, + 'customerItemId' => 2, + 'guestOrderId' => 11, + 'customerOrderId' => 22, + 'expectedItemId' => 1, + 'expectedOrderId' => 11 + ] + ]; + } + + /** + * Gets quote by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuote(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + + return array_pop($items); + } } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php new file mode 100644 index 0000000000000..922ee35a9df5e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model; + +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Class QuoteValidatorTest. + * + * @magentoDbIsolation enabled + */ +class QuoteValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var QuoteValidator + */ + private $quoteValidator; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->quoteValidator = Bootstrap::getObjectManager()->create(QuoteValidator::class); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Please check the shipping address information. + */ + public function testValidateBeforeSubmitShippingAddressInvalid() + { + $quote = $this->getQuote(); + $quote->getShippingAddress()->setPostcode(''); + + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Some addresses can't be used due to the configurations for specific countries. + */ + public function testValidateBeforeSubmitCountryIsNotAllowed() + { + /** @magentoConfigFixture does not allow to change the value for the website scope */ + Bootstrap::getObjectManager()->get( + \Magento\Framework\App\Config\MutableScopeConfigInterface::class + )->setValue( + 'general/country/allow', + 'US', + \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES + ); + $quote = $this->getQuote(); + $quote->getShippingAddress()->setCountryId('AF'); + + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The shipping method is missing. Select the shipping method and try again. + */ + public function testValidateBeforeSubmitShippingMethodInvalid() + { + $quote = $this->getQuote(); + $quote->getShippingAddress()->setShippingMethod('NONE'); + + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Please check the billing address information. + */ + public function testValidateBeforeSubmitBillingAddressInvalid() + { + $quote = $this->getQuote(); + $quote->getBillingAddress()->setTelephone(''); + + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Enter a valid payment method and try again. + */ + public function testValidateBeforeSubmitPaymentMethodInvalid() + { + $quote = $this->getQuote(); + $quote->getPayment()->setMethod(''); + + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @magentoConfigFixture current_store sales/minimum_order/active 1 + * @magentoConfigFixture current_store sales/minimum_order/amount 100 + */ + public function testValidateBeforeSubmitMinimumAmountInvalid() + { + $quote = $this->getQuote(); + $quote->getShippingAddress() + ->setBaseSubtotal(0); + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @return void + */ + public function testValidateBeforeSubmitWithoutMinimumOrderAmount() + { + $this->quoteValidator->validateBeforeSubmit($this->getQuote()); + } + + /** + * @magentoConfigFixture current_store sales/minimum_order/active 1 + * @magentoConfigFixture current_store sales/minimum_order/amount 100 + */ + public function testValidateBeforeSubmitWithMinimumOrderAmount() + { + $quote = $this->getQuote(); + $quote->getShippingAddress() + ->setBaseSubtotal(200); + $this->quoteValidator->validateBeforeSubmit($quote); + } + + /** + * @return Quote + */ + private function getQuote(): Quote + { + /** @var Quote $quote */ + $quote = Bootstrap::getObjectManager()->create(Quote::class); + + /** @var AddressInterface $billingAddress */ + $billingAddress = Bootstrap::getObjectManager()->create(AddressInterface::class); + $billingAddress->setFirstname('Joe') + ->setLastname('Doe') + ->setCountryId('US') + ->setRegion('TX') + ->setCity('Austin') + ->setStreet('1000 West Parmer Line') + ->setPostcode('11501') + ->setTelephone('123456789'); + $quote->setBillingAddress($billingAddress); + + /** @var AddressInterface $shippingAddress */ + $shippingAddress = Bootstrap::getObjectManager()->create(AddressInterface::class); + $shippingAddress->setFirstname('Joe') + ->setLastname('Doe') + ->setCountryId('US') + ->setRegion('TX') + ->setCity('Austin') + ->setStreet('1000 West Parmer Line') + ->setPostcode('11501') + ->setTelephone('123456789'); + $quote->setShippingAddress($shippingAddress); + + $quote->getShippingAddress() + ->setShippingMethod('flatrate_flatrate') + ->setCollectShippingRates(true); + /** @var Rate $shippingRate */ + $shippingRate = Bootstrap::getObjectManager()->create(Rate::class); + $shippingRate->setMethod('flatrate') + ->setCarrier('flatrate') + ->setPrice('5') + ->setCarrierTitle('Flat Rate') + ->setCode('flatrate_flatrate'); + $quote->getShippingAddress() + ->addShippingRate($shippingRate); + + $quote->getPayment()->setMethod('CC'); + + /** @var QuoteRepository $quoteRepository */ + $quoteRepository = Bootstrap::getObjectManager()->create(QuoteRepository::class); + $quoteRepository->save($quote); + + return $quote; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/Observer/ProcessProductAfterDeleteEventObserverTest.php b/dev/tests/integration/testsuite/Magento/Review/Observer/ProcessProductAfterDeleteEventObserverTest.php new file mode 100644 index 0000000000000..8c1fd8eca70f9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/Observer/ProcessProductAfterDeleteEventObserverTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Review\Observer; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Review\Model\ResourceModel\Review\Collection as ReviewCollection; +use Magento\Review\Model\ResourceModel\Review\CollectionFactory as ReviewCollectionFactory; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test checks that product review is removed when the corresponding product is removed + */ +class ProcessProductAfterDeleteEventObserverTest extends AbstractController +{ + /** + * @magentoDataFixture Magento/Review/_files/customer_review.php + */ + public function testReviewIsRemovedWhenProductDeleted() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple'); + + /** @var ReviewCollection $reviewsCollection */ + $reviewsCollection = $objectManager->get(ReviewCollectionFactory::class)->create(); + $reviewsCollection->addEntityFilter('product', $product->getId()); + + self::assertEquals(1, $reviewsCollection->count()); + + /* Remove product and ensure that the product review is removed as well */ + $productRepository->delete($product); + $reviewsCollection->clear(); + + self::assertEquals(0, $reviewsCollection->count()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Items/Column/NameTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Items/Column/NameTest.php new file mode 100644 index 0000000000000..26bdbf3a6cfde --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Items/Column/NameTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml\Items\Column; + +/** + * @magentoAppArea adminhtml + */ +class NameTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Name + */ + private $block; + + protected function setUp() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var $layout \Magento\Framework\View\Layout */ + $layout = $objectManager->create(\Magento\Framework\View\LayoutInterface::class); + /** @var $block \Magento\Sales\Block\Adminhtml\Items\AbstractItems */ + $this->block = $layout->createBlock(Name::class, 'block'); + } + + public function testTruncateString() : void + { + $remainder = ''; + $this->assertEquals( + '12345', + $this->block->truncateString('1234567890', 5, '', $remainder) + ); + } + + public function testGetFormattedOptiong() : void + { + $this->assertEquals( + [ + 'value' => '1234567890123456789012345678901234567890123456789012345', + 'remainder' => '67890', + ], + $this->block->getFormattedOption( + '123456789012345678901234567890123456789012345678901234567890' + ) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index 6b4e9a66f4c6b..999522a49e006 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -5,39 +5,50 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; +use Magento\Backend\Model\Session\Quote as SessionQuote; +use Magento\Customer\Api\Data\AttributeMetadataInterfaceFactory; +use Magento\Customer\Model\Metadata\Form; +use Magento\Customer\Model\Metadata\FormFactory; +use Magento\Framework\View\LayoutInterface; +use Magento\Quote\Model\Quote; +use Magento\TestFramework\Helper\Bootstrap; + /** * @magentoAppArea adminhtml */ class AccountTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\Sales\Block\Adminhtml\Order\Create\Form\Account */ - protected $_accountBlock; + /** @var Account */ + private $accountBlock; /** - * @var \Magento\TestFramework\Helper\Bootstrap + * @var Bootstrap */ - protected $_objectManager; + private $objectManager; /** * @magentoDataFixture Magento/Sales/_files/quote.php */ protected function setUp() { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $quote = $this->_objectManager->create(\Magento\Quote\Model\Quote::class)->load(1); + $this->objectManager = Bootstrap::getObjectManager(); + $quote = $this->objectManager->create(Quote::class)->load(1); $sessionQuoteMock = $this->getMockBuilder( - \Magento\Backend\Model\Session\Quote::class + SessionQuote::class )->disableOriginalConstructor()->setMethods( ['getCustomerId', 'getStore', 'getStoreId', 'getQuote'] )->getMock(); $sessionQuoteMock->expects($this->any())->method('getCustomerId')->will($this->returnValue(1)); $sessionQuoteMock->expects($this->any())->method('getQuote')->will($this->returnValue($quote)); - /** @var \Magento\Framework\View\LayoutInterface $layout */ - $layout = $this->_objectManager->get(\Magento\Framework\View\LayoutInterface::class); - $this->_accountBlock = $layout->createBlock( - \Magento\Sales\Block\Adminhtml\Order\Create\Form\Account::class, + /** @var LayoutInterface $layout */ + $layout = $this->objectManager->get(LayoutInterface::class); + $this->accountBlock = $layout->createBlock( + Account::class, 'address_block' . rand(), ['sessionQuote' => $sessionQuoteMock] ); @@ -50,7 +61,7 @@ protected function setUp() public function testGetForm() { $expectedFields = ['group_id', 'email']; - $form = $this->_accountBlock->getForm(); + $form = $this->accountBlock->getForm(); $this->assertEquals(1, $form->getElements()->count(), "Form has invalid number of fieldsets"); $fieldset = $form->getElements()[0]; @@ -63,4 +74,56 @@ public function testGetForm() ); } } + + /** + * Tests a case when user defined custom attribute has default value. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testGetFormWithUserDefinedAttribute() + { + $formFactory = $this->getFormFactoryMock(); + $this->objectManager->addSharedInstance($formFactory, FormFactory::class); + + /** @var LayoutInterface $layout */ + $layout = $this->objectManager->get(LayoutInterface::class); + $accountBlock = $layout->createBlock(Account::class, 'address_block' . rand()); + + $form = $accountBlock->getForm(); + $form->setUseContainer(true); + + $this->assertContains( + '<option value="1" selected="selected">Yes</option>', + $form->toHtml(), + 'Default value for user defined custom attribute should be selected' + ); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getFormFactoryMock(): \PHPUnit_Framework_MockObject_MockObject + { + /** @var AttributeMetadataInterfaceFactory $attributeMetadataFactory */ + $attributeMetadataFactory = $this->objectManager->create(AttributeMetadataInterfaceFactory::class); + $booleanAttribute = $attributeMetadataFactory->create() + ->setAttributeCode('boolean') + ->setBackendType('boolean') + ->setFrontendInput('boolean') + ->setDefaultValue('1') + ->setFrontendLabel('Yes/No'); + + $form = $this->getMockBuilder(Form::class) + ->disableOriginalConstructor() + ->getMock(); + $form->method('getUserAttributes')->willReturn([$booleanAttribute]); + $form->method('getSystemAttributes')->willReturn([]); + + $formFactory = $this->getMockBuilder(FormFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $formFactory->method('create')->willReturn($form); + + return $formFactory; + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php index b6df7eb05c03d..abdbab2c24d16 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/FormTest.php @@ -1,151 +1,201 @@ <?php /** - * Test class for \Magento\Sales\Block\Adminhtml\Order\Create\Form + * Test class for Form * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Sales\Block\Adminhtml\Order\Create; +use Magento\Backend\Model\Session\Quote as QuoteSession; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Customer\Api\Data\RegionInterfaceFactory; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + /** * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FormTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\Sales\Block\Adminhtml\Order\Create\Form */ - protected $_orderCreateBlock; + /** + * @var Form + */ + private $block; - /** @var \Magento\Framework\ObjectManagerInterface */ - protected $_objectManager; + /** + * @var ObjectManagerInterface + */ + private $objectManager; /** - * @magentoDataFixture Magento/Sales/_files/quote.php + * @var QuoteSession|MockObject */ + private $session; + protected function setUp() { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - $sessionMock = $this->getMockBuilder( - \Magento\Backend\Model\Session\Quote::class - )->disableOriginalConstructor()->setMethods( - ['getCustomerId', 'getQuote', 'getStoreId', 'getStore'] - )->getMock(); - $sessionMock->expects($this->any())->method('getCustomerId')->will($this->returnValue(1)); - - $quote = $this->_objectManager->create(\Magento\Quote\Model\Quote::class)->load(1); - $sessionMock->expects($this->any())->method('getQuote')->will($this->returnValue($quote)); - - $sessionMock->expects($this->any())->method('getStoreId')->will($this->returnValue(1)); - - $storeMock = $this->getMockBuilder( - \Magento\Store\Model\Store::class - )->disableOriginalConstructor()->setMethods( - ['getCurrentCurrencyCode'] - )->getMock(); - $storeMock->expects($this->any())->method('getCurrentCurrencyCode')->will($this->returnValue('USD')); - $sessionMock->expects($this->any())->method('getStore')->will($this->returnValue($storeMock)); - - /** @var \Magento\Framework\View\LayoutInterface $layout */ - $layout = $this->_objectManager->get(\Magento\Framework\View\LayoutInterface::class); - $this->_orderCreateBlock = $layout->createBlock( - \Magento\Sales\Block\Adminhtml\Order\Create\Form::class, - 'order_create_block' . rand(), - ['sessionQuote' => $sessionMock] + $this->objectManager = Bootstrap::getObjectManager(); + + $this->session = $this->getMockBuilder(QuoteSession::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerId', 'getQuote', 'getStoreId', 'getStore', 'getQuoteId']) + ->getMock(); + $this->session->method('getCustomerId') + ->willReturn(1); + + $this->session->method('getStoreId') + ->willReturn(1); + + $store = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->setMethods(['getCurrentCurrencyCode']) + ->getMock(); + $store->method('getCurrentCurrencyCode') + ->willReturn('USD'); + $this->session->method('getStore') + ->willReturn($store); + + /** @var LayoutInterface $layout */ + $layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $layout->createBlock( + Form::class, + 'order_create_block' . mt_rand(), + ['sessionQuote' => $this->session] ); parent::setUp(); } /** + * Checks if all needed order's data is correctly returned to the form. + * * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Sales/_files/quote.php */ public function testOrderDataJson() { - /** @var array $addressIds */ - $addressIds = $this->setUpMockAddress(); - $orderDataJson = $this->_orderCreateBlock->getOrderDataJson(); - $expectedOrderDataJson = <<<ORDER_DATA_JSON - { - "customer_id":1, - "addresses": - {"{$addressIds[0]}": - {"firstname":"John","lastname":"Smith","company":false,"street":"Green str, 67","city":"CityM", - "country_id":"US", - "region":"Alabama","region_id":1, - "postcode":"75477","telephone":"3468676","vat_id":false}, - "{$addressIds[1]}": - {"firstname":"John","lastname":"Smith","company":false,"street":"Black str, 48","city":"CityX", - "country_id":"US", - "region":"Alabama","region_id":1, - "postcode":"47676","telephone":"3234676","vat_id":false} - }, - "store_id":1,"currency_symbol":"$","shipping_method_reseted":true,"payment_method":null - } -ORDER_DATA_JSON; - - $this->assertEquals(json_decode($expectedOrderDataJson), json_decode($orderDataJson)); + $customerId = 1; + $quote = $this->getQuote('test01'); + $this->session->method('getQuote') + ->willReturn($quote); + $this->session->method('getQuoteId') + ->willReturn($quote->getId()); + $addressData = $this->getAddressData(); + $addressIds = $this->setUpMockAddress($customerId, $addressData); + $expected = [ + 'customer_id' => $customerId, + 'addresses' => [ + $addressIds[0] => $addressData[0], + $addressIds[1] => $addressData[1] + ], + 'store_id' => 1, + 'currency_symbol' => '$', + 'shipping_method_reseted' => true, + 'payment_method' => 'checkmo', + 'quote_id' => $quote->getId() + ]; + + self::assertEquals($expected, json_decode($this->block->getOrderDataJson(), true)); } - private function setUpMockAddress() + /** + * Saves customer's addresses. + * + * @param int $customerId + * @param array $addressData + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function setUpMockAddress(int $customerId, array $addressData) { - /** @var \Magento\Customer\Api\Data\RegionInterfaceFactory $regionFactory */ - $regionFactory = $this->_objectManager->create(\Magento\Customer\Api\Data\RegionInterfaceFactory::class); - - /** @var \Magento\Customer\Api\Data\AddressInterfaceFactory $addressFactory */ - $addressFactory = $this->_objectManager->create(\Magento\Customer\Api\Data\AddressInterfaceFactory::class); - /** @var \Magento\Customer\Api\AddressRepositoryInterface $addressRepository */ - $addressRepository = $this->_objectManager->create(\Magento\Customer\Api\AddressRepositoryInterface::class); - - $addressData1 = $addressFactory->create()->setCountryId( - 'US' - )->setCustomerId( - 1 - )->setIsDefaultBilling( - true - )->setIsDefaultShipping( - true - )->setPostcode( - '75477' - )->setRegion( - $regionFactory->create()->setRegionCode('AL')->setRegion('Alabama')->setRegionId(1) - )->setStreet( - ['Green str, 67'] - )->setTelephone( - '3468676' - )->setCity( - 'CityM' - )->setFirstname( - 'John' - )->setLastname( - 'Smith' - ); - - $addressData2 = $addressFactory->create()->setCountryId( - 'US' - )->setCustomerId( - 1 - )->setIsDefaultBilling( - false - )->setIsDefaultShipping( - false - )->setPostcode( - '47676' - )->setRegion( - $regionFactory->create()->setRegionCode('AL')->setRegion('Alabama')->setRegionId(1) - )->setStreet( - ['Black str, 48'] - )->setCity( - 'CityX' - )->setTelephone( - '3234676' - )->setFirstname( - 'John' - )->setLastname( - 'Smith' - ); + /** @var RegionInterfaceFactory $regionFactory */ + $regionFactory = $this->objectManager->create(RegionInterfaceFactory::class); + /** @var AddressInterfaceFactory $addressFactory */ + $addressFactory = $this->objectManager->create(AddressInterfaceFactory::class); + /** @var AddressRepositoryInterface $addressRepository */ + $addressRepository = $this->objectManager->create(AddressRepositoryInterface::class); + $region = $regionFactory->create() + ->setRegionCode('AL') + ->setRegion('Alabama') + ->setRegionId(1); + + $ids = []; + foreach ($addressData as $data) { + $address = $addressFactory->create(['data' => $data]); + $address->setRegion($region) + ->setCustomerId($customerId) + ->setIsDefaultBilling(true) + ->setIsDefaultShipping(true); + $address = $addressRepository->save($address); + $ids[] = $address->getId(); + } + + return $ids; + } - $savedAddress1 = $addressRepository->save($addressData1); - $savedAddress2 = $addressRepository->save($addressData2); + /** + * Gets test address data. + * + * @return array + */ + private function getAddressData(): array + { + return [ + [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => false, + 'street' => 'Green str, 67', + 'city' => 'CityM', + 'country_id' => 'US', + 'region' => 'Alabama', + 'region_id' => 1, + 'postcode' => '75477', + 'telephone' => '3468676', + 'vat_id' => false + ], + [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => false, + 'street' => 'Black str, 48', + 'city' => 'CityX', + 'country_id' => 'US', + 'region' => 'Alabama', + 'region_id' => 1, + 'postcode' => '47676', + 'telephone' => '3234676', + 'vat_id' => false, + ] + ]; + } - return [$savedAddress1->getId(), $savedAddress2->getId()]; + /** + * Gets quote by ID. + * + * @param string $reservedOrderId + * @return Quote + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getQuote(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + /** @var CartRepositoryInterface $repository */ + $repository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddCommentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddCommentTest.php deleted file mode 100644 index 020c9f51bb10f..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddCommentTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class AddCommentTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::comment'; - $this->uri = 'backend/sales/order/addcomment'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressSaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressSaveTest.php deleted file mode 100644 index 9cdd84a5971f3..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressSaveTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class AddressSaveTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::actions_edit'; - $this->uri = 'backend/sales/order/addresssave'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressTest.php deleted file mode 100644 index 73e46b513287f..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/AddressTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class AddressTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::actions_edit'; - $this->uri = 'backend/sales/order/address'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CancelTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CancelTest.php deleted file mode 100644 index c24191fe5e45e..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CancelTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class CancelTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::cancel'; - $this->uri = 'backend/sales/order/cancel'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php deleted file mode 100644 index 70fbb2ee9a5bd..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class EmailTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::email'; - $this->uri = 'backend/sales/order/email'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/HoldTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/HoldTest.php deleted file mode 100644 index 4c90939d75b2d..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/HoldTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class HoldTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::hold'; - $this->uri = 'backend/sales/order/hold'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ReviewPaymentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ReviewPaymentTest.php deleted file mode 100644 index 96d197628584a..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ReviewPaymentTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class ReviewPaymentTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::review_payment'; - $this->uri = 'backend/sales/order/reviewpayment'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/UnholdTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/UnholdTest.php deleted file mode 100644 index 351801d8a0558..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/UnholdTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class UnholdTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::unhold'; - $this->uri = 'backend/sales/order/unhold'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ViewTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ViewTest.php deleted file mode 100644 index 0374edfaad9d9..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/ViewTest.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Order; - -class ViewTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::actions_view'; - $this->uri = 'backend/sales/order/view'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Transactions/FetchTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Transactions/FetchTest.php deleted file mode 100644 index 7b9481db7997a..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Transactions/FetchTest.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/*** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Controller\Adminhtml\Transactions; - -use Magento\TestFramework\TestCase\AbstractBackendController; - -class FetchTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function setUp() - { - $this->resource = 'Magento_Sales::transactions_fetch'; - $this->uri = 'backend/sales/transactions/fetch'; - parent::setUp(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php index 1bad0eec7d1d0..d9c38d5697d96 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php @@ -36,6 +36,9 @@ class RendererTest extends \PHPUnit\Framework\TestCase */ private $config; + /** + * Set up + */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); @@ -46,7 +49,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Sales/_files/order_fixture_store.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testFormat() diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentDocumentFactoryTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentDocumentFactoryTest.php new file mode 100644 index 0000000000000..7f0ec2ae92f89 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentDocumentFactoryTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order; + +use Magento\Sales\Api\Data\ShipmentCreationArgumentsExtensionInterfaceFactory; +use Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface; +use Magento\Sales\Model\Order; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Provide tests for shipment document factory. + */ +class ShipmentDocumentFactoryTest extends TestCase +{ + /** + * @var Order + */ + private $order; + + /** + * @var ShipmentDocumentFactory + */ + private $shipmentDocumentFactory; + + /** + * @var ShipmentCreationArgumentsInterface + */ + private $shipmentCreationArgumentsInterface; + + /** + * @var ShipmentCreationArgumentsExtensionInterfaceFactory + */ + private $shipmentCreationArgumentsExtensionInterfaceFactory; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + + $this->order = $objectManager->create(Order::class); + $this->shipmentDocumentFactory = $objectManager->create(ShipmentDocumentFactory::class); + $this->shipmentCreationArgumentsInterface = $objectManager + ->create(ShipmentCreationArgumentsInterface::class); + $this->shipmentCreationArgumentsExtensionInterfaceFactory = $objectManager + ->create(ShipmentCreationArgumentsExtensionInterfaceFactory::class); + } + + /** + * Create shipment with shipment creation arguments. + * + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testCreate(): void + { + $order = $this->order->loadByIncrementId('100000001'); + $argumentsExtensionAttributes = $this->shipmentCreationArgumentsExtensionInterfaceFactory->create([ + 'data' => ['test_attribute_value' => 'test_value'] + ]); + $this->shipmentCreationArgumentsInterface->setExtensionAttributes($argumentsExtensionAttributes); + $shipment = $this->shipmentDocumentFactory->create( + $order, + [], + [], + null, + false, + [], + $this->shipmentCreationArgumentsInterface + ); + $shipmentExtensionAttributes = $shipment->getExtensionAttributes(); + self::assertEquals('test_value', $shipmentExtensionAttributes->getTestAttributeValue()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentTest.php index 18097cf12def4..0679fc6ffe6cb 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/ShipmentTest.php @@ -3,35 +3,59 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Sales\Model\Order; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Payment\Helper\Data; +use Magento\Sales\Api\Data\CommentInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Api\Data\ShipmentItemInterface; +use Magento\Sales\Api\Data\ShipmentTrackInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + /** - * Class ShipmentTest * @magentoAppIsolation enabled - * @package Magento\Sales\Model\Order + * @magentoDataFixture Magento/Sales/_files/order.php */ class ShipmentTest extends \PHPUnit\Framework\TestCase { + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ShipmentRepositoryInterface + */ + private $shipmentRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->shipmentRepository = $this->objectManager->get(ShipmentRepositoryInterface::class); + } + /** * Check the correctness and stability of set/get packages of shipment * - * @magentoDataFixture Magento/Sales/_files/order.php + * @magentoAppArea frontend */ public function testPackages() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->get(\Magento\Framework\App\State::class)->setAreaCode('frontend'); - $order = $objectManager->create(\Magento\Sales\Model\Order::class); - $order->loadByIncrementId('100000001'); - $order->setCustomerEmail('customer@example.com'); + $order = $this->getOrder('100000001'); $payment = $order->getPayment(); - $paymentInfoBlock = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Payment\Helper\Data::class - )->getInfoBlock( - $payment - ); + $paymentInfoBlock = $this->objectManager->get(Data::class) + ->getInfoBlock($payment); $payment->setBlockMock($paymentInfoBlock); $items = []; @@ -39,47 +63,84 @@ public function testPackages() $items[$item->getId()] = $item->getQtyOrdered(); } /** @var \Magento\Sales\Model\Order\Shipment $shipment */ - $shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); + $shipment = $this->objectManager->get(ShipmentFactory::class)->create($order, $items); $packages = [['1'], ['2']]; $shipment->setPackages($packages); - $this->assertEquals($packages, $shipment->getPackages()); - $shipment->save(); - $shipment->save(); - $shipment->load($shipment->getId()); - $this->assertEquals($packages, $shipment->getPackages()); + $saved = $this->shipmentRepository->save($shipment); + self::assertEquals($packages, $saved->getPackages()); } /** * Check that getTracksCollection() always return collection instance. - * - * @magentoDataFixture Magento/Sales/_files/order.php */ public function testAddTrack() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $order = $this->getOrder('100000001'); - $order = $objectManager->create(\Magento\Sales\Model\Order::class); - $order->loadByIncrementId('100000001'); + /** @var ShipmentTrackInterface $track */ + $track = $this->objectManager->create(ShipmentTrackInterface::class); + $track->setNumber('Test Number') + ->setTitle('Test Title') + ->setCarrierCode('Test CODE'); $items = []; foreach ($order->getItems() as $item) { $items[$item->getId()] = $item->getQtyOrdered(); } /** @var \Magento\Sales\Model\Order\Shipment $shipment */ - $shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); + $shipment = $this->objectManager->get(ShipmentFactory::class)->create($order, $items); + $shipment->addTrack($track); $shipment->save(); + $saved = $this->shipmentRepository->save($shipment); + self::assertNotEmpty($saved->getTracks()); + } + + /** + * Checks adding comment to the shipment entity. + */ + public function testAddComment() + { + $message1 = 'Test Comment 1'; + $message2 = 'Test Comment 2'; + $order = $this->getOrder('100000001'); + + /** @var ShipmentInterface $shipment */ + $shipment = $this->objectManager->create(ShipmentInterface::class); + $shipment->setOrder($order) + ->addItem($this->objectManager->create(ShipmentItemInterface::class)) + ->addComment($message1) + ->addComment($message2); - /** @var $track \Magento\Sales\Model\Order\Shipment\Track */ - $track = $objectManager->get(\Magento\Sales\Model\Order\Shipment\Track::class); - $track->setNumber('Test Number')->setTitle('Test Title')->setCarrierCode('Test CODE'); + $saved = $this->shipmentRepository->save($shipment); + + $comments = $saved->getComments(); + $actual = array_map(function (CommentInterface $comment) { + return $comment->getComment(); + }, $comments); + self::assertEquals(2, count($actual)); + self::assertEquals([$message1, $message2], $actual); + } + + /** + * Gets order entity by increment id. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrder(string $incrementId): OrderInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', $incrementId) + ->create(); - $this->assertEmpty($shipment->getTracks()); - $shipment->addTrack($track)->save(); + /** @var OrderRepositoryInterface $repository */ + $repository = $this->objectManager->get(OrderRepositoryInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); - //to empty cache - $shipment->setTracks(null); - $this->assertNotEmpty($shipment->getTracks()); + return array_pop($items); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php new file mode 100644 index 0000000000000..c0de639ea7429 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Payment/EncryptionUpdateTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sales\Model\ResourceModel\Order\Payment; + +use Magento\Framework\Encryption\Encryptor; +use Magento\TestFramework\Helper\Bootstrap; + +class EncryptionUpdateTest extends \PHPUnit\Framework\TestCase +{ + const TEST_CC_NUMBER = '4111111111111111'; + + /** + * Tests re-encryption of credit card numbers + * + * @magentoDataFixture Magento/Sales/_files/payment_enc_cc.php + */ + public function testReEncryptCreditCardNumbers() + { + $objectManager = Bootstrap::getObjectManager(); + + /** @var \Magento\Framework\Encryption\EncryptorInterface $encyptor */ + $encyptor = $objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); + + /** @var \Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate $resource */ + $resource = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdate::class); + $resource->reEncryptCreditCardNumbers(); + + /** @var \Magento\Sales\Model\ResourceModel\Order\Payment\Collection $collection */ + $collection = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment\Collection::class); + $collection->addFieldToFilter('cc_number_enc', ['notnull' => true]); + + $this->assertGreaterThan(0, $collection->getTotalCount()); + + /** @var \Magento\Sales\Model\Order\Payment $payment */ + foreach ($collection->getItems() as $payment) { + $this->assertEquals( + static::TEST_CC_NUMBER, + $encyptor->decrypt($payment->getCcNumberEnc()) + ); + + $this->assertStringStartsWith('0:' . Encryptor::CIPHER_LATEST . ':', $payment->getCcNumberEnc()); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/address_data.php b/dev/tests/integration/testsuite/Magento/Sales/_files/address_data.php index 2d131bf5f57e2..394b13078010a 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/address_data.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/address_data.php @@ -6,6 +6,7 @@ return [ 'region' => 'CA', + 'region_id' => '12', 'postcode' => '11111', 'lastname' => 'lastname', 'firstname' => 'firstname', diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_info.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_info.php index 31aa8aafd94d9..f3e528b9c0d28 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_info.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_info.php @@ -85,6 +85,9 @@ $invoice = $invoiceFactory->prepareInvoice($order, [$item->getId() => 10]); $invoice->register(); $invoice->save(); +$order->save(); + +$invoice = $objectManager->get(\Magento\Sales\Api\InvoiceRepositoryInterface::class)->get($invoice->getId()); /** @var \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory */ $creditmemoFactory = $objectManager->get(\Magento\Sales\Model\Order\CreditmemoFactory::class); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php b/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php new file mode 100644 index 0000000000000..bfa643fcf5f99 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/payment_enc_cc.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteria; +use Magento\Sales\Model\ResourceModel\Order\Payment\EncryptionUpdateTest; +use Magento\Framework\App\DeploymentConfig; + +require 'order.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var DeploymentConfig $deployConfig */ +$deployConfig = $objectManager->get(DeploymentConfig::class); + +/** + * Creates an encrypted card number with the current crypt key using + * a legacy cipher. + */ +// @codingStandardsIgnoreStart +$handle = @mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, ''); +$initVectorSize = @mcrypt_enc_get_iv_size($handle); +$initVector = str_repeat("\0", $initVectorSize); +@mcrypt_generic_init($handle, $deployConfig->get('crypt/key'), $initVector); + +$encCcNumber = @mcrypt_generic($handle, EncryptionUpdateTest::TEST_CC_NUMBER); + +@mcrypt_generic_deinit($handle); +@mcrypt_module_close($handle); +// @codingStandardsIgnoreEnd + +/** @var SearchCriteria $searchCriteria */ +$searchCriteria = $objectManager->get(SearchCriteriaBuilder::class) + ->addFilter('increment_id', '100000001') + ->create(); + +$orders = $orderRepository->getList($searchCriteria)->getItems(); +$order = array_pop($orders); + +/** @var \Magento\Sales\Model\ResourceModel\Order\Payment $resource */ +$resource = $objectManager->create(\Magento\Sales\Model\ResourceModel\Order\Payment::class); +$resource->getConnection()->insert( + $resource->getMainTable(), + [ + 'parent_id' => $order->getId(), + 'cc_number_enc' => '0:2:' . base64_encode($encCcNumber), + ] +); diff --git a/dev/tests/integration/testsuite/Magento/Sales/etc/extension_attributes.xml b/dev/tests/integration/testsuite/Magento/Sales/etc/extension_attributes.xml new file mode 100644 index 0000000000000..abefb087024ef --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/etc/extension_attributes.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd"> + <extension_attributes for="Magento\Sales\Api\Data\ShipmentInterface"> + <attribute code="test_attribute_value" type="string"/> + </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\ShipmentCreationArgumentsInterface"> + <attribute code="test_attribute_value" type="string"/> + </extension_attributes> +</config> diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php index e601e2dd59232..694310f2cbc89 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php @@ -93,6 +93,36 @@ public function applyFixedDiscountDataProvider(): array ]; } + /** + * Tests that coupon with wildcard symbols in code can be successfully applied. + * + * @magentoDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testCouponCodeWithWildcard() + { + $expectedDiscount = '-5.00'; + $couponCode = '2?ds5!2d'; + $cartId = $this->cartManagement->createEmptyCart(); + $productPrice = 10; + + $product = $this->createProduct($productPrice); + + /** @var CartItemInterface $quoteItem */ + $quoteItem = Bootstrap::getObjectManager()->create(CartItemInterface::class); + $quoteItem->setQuoteId($cartId); + $quoteItem->setProduct($product); + $quoteItem->setQty(1); + $this->cartItemRepository->save($quoteItem); + + $this->couponManagement->set($cartId, $couponCode); + + /** @var GuestCartTotalRepositoryInterface $cartTotalRepository */ + $cartTotalRepository = Bootstrap::getObjectManager()->get(GuestCartTotalRepositoryInterface::class); + $total = $cartTotalRepository->get($cartId); + + $this->assertEquals($expectedDiscount, $total->getBaseDiscountAmount()); + } + /** * Returns simple product with given price. * diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php index 98826adeb2147..00f1b1f451815 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Condition/ProductTest.php @@ -6,6 +6,14 @@ namespace Magento\SalesRule\Model\Rule\Condition; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\Data\CartInterface; +use Magento\SalesRule\Api\RuleRepositoryInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ProductTest extends \PHPUnit\Framework\TestCase { /** @@ -98,4 +106,71 @@ public function validateProductConditionDataProvider() ] ]; } + + /** + * Ensure that SalesRules filtering on quote items quantity validates configurable product correctly + * + * 1. Load a quote with a configured product and a sales rule set to filter items with quantity 2. + * 2. Attempt to validate the sales rule against the quote and assert the output is negative. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + * @magentoDataFixture Magento/SalesRule/_files/cart_rule_10_percent_off.php + */ + public function testValidateQtySalesRuleWithConfigurable() + { + // Load the quote that contains a child of a configurable product with quantity 1. + $quote = $this->getQuote('test_cart_with_configurable'); + + // Load the SalesRule looking for products with quantity 2. + $rule = $this->getSalesRule('10% Off on orders with two items'); + + $this->assertFalse( + $rule->validate($quote->getBillingAddress()) + ); + } + + /** + * Gets quote by reserved order id. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote($reservedOrderId) + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + return array_pop($items); + } + + /** + * Gets rule by name. + * + * @param string $name + * @return \Magento\SalesRule\Model\Rule + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getSalesRule(string $name): \Magento\SalesRule\Model\Rule + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $ruleRepository = $this->objectManager->get(RuleRepositoryInterface::class); + $items = $ruleRepository->getList($searchCriteria)->getItems(); + + $rule = array_pop($items); + /** @var \Magento\SalesRule\Model\Converter\ToModel $converter */ + $converter = $this->objectManager->get(\Magento\SalesRule\Model\Converter\ToModel::class); + + return $converter->toModel($rule); + } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_10_percent_off.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_10_percent_off.php new file mode 100644 index 0000000000000..0761e5753da75 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_10_percent_off.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Model\GroupManagement; +use Magento\SalesRule\Model\Rule; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$websiteId = Bootstrap::getObjectManager()->get(StoreManagerInterface::class) + ->getWebsite() + ->getId(); + +/** @var Rule $salesRule */ +$salesRule = $objectManager->create(Rule::class); +$salesRule->setData( + [ + 'name' => '10% Off on orders with two items', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_NO_COUPON, + 'simple_action' => 'by_percent', + 'discount_amount' => 10, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [$websiteId] + ] +); + +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product\Subselect::class, + 'attribute' => 'qty', + 'operator' => '==', + 'value' => '2', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Product::class, + 'attribute' => 'attribute_set_id', + 'operator' => '==', + 'value' => '4', + 'is_value_processed' => false, + ], + ], + ], + ], +]); + +$salesRule->save(); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard.php new file mode 100644 index 0000000000000..9005284f984cf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Model\GroupManagement; +use Magento\SalesRule\Api\CouponRepositoryInterface; +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\Rule; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Rule $salesRule */ +$salesRule = $objectManager->create(Rule::class); +$salesRule->setData( + [ + 'name' => '5$ fixed discount on whole cart', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, + 'conditions' => [], + 'simple_action' => Rule::CART_FIXED_ACTION, + 'discount_amount' => 5, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'website_ids' => [ + $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] +); +$objectManager->get(\Magento\SalesRule\Model\ResourceModel\Rule::class)->save($salesRule); + +// Create coupon and assign "15$ fixed discount" rule to this coupon. +$coupon = $objectManager->create(Coupon::class); +$coupon->setRuleId($salesRule->getId()) + ->setCode('2?ds5!2d') + ->setType(0); + +/** @var CouponRepositoryInterface $couponRepository */ +$couponRepository = $objectManager->get(CouponRepositoryInterface::class); +$couponRepository->save($coupon); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php new file mode 100644 index 0000000000000..776c302210351 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_code_with_wildcard_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\SalesRule\Api\CouponRepositoryInterface; +use Magento\SalesRule\Api\RuleRepositoryInterface; +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Rule $salesRule */ +$salesRule = getSalesRule('5$ fixed discount on whole cart'); +if ($salesRule !== null) { + /** @var RuleRepositoryInterface $ruleRepository */ + $ruleRepository = $objectManager->get(RuleRepositoryInterface::class); + $ruleRepository->deleteById($salesRule->getRuleId()); +} + +$coupon = $objectManager->create(Coupon::class); +$coupon->loadByCode('2?ds5!2d'); +if ($coupon->getCouponId()) { + /** @var CouponRepositoryInterface $couponRepository */ + $couponRepository = $objectManager->get(CouponRepositoryInterface::class); + $couponRepository->deleteById($coupon->getCouponId()); +} + +function getSalesRule(string $name) +{ + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = Bootstrap::getObjectManager()->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('name', $name) + ->create(); + + /** @var RuleRepositoryInterface $ruleRepository */ + $ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class); + $items = $ruleRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); +} diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php new file mode 100644 index 0000000000000..c25dc65ccd73c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Setup\Console\Command; + +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Framework\Console\Cli; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Test command that sets indexer mode for catalog_product_price indexer + */ +class PriceIndexerDimensionsModeSetCommandTest extends \Magento\TestFramework\Indexer\TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var \Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand */ + private $command; + + /** @var CommandTester */ + private $commandTester; + + /** + * setUp + */ + public function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + $this->objectManager->get(\Magento\TestFramework\App\Config::class)->clean(); + + $this->command = $this->objectManager->create( + \Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand::class + ); + + $this->commandTester = new CommandTester($this->command); + + parent::setUp(); + } + + /** + * setUpBeforeClass + */ + public static function setUpBeforeClass() + { + $db = Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + + /** + * @magentoAppArea adminhtml + * @magentoAppIsolation enabled + * + * @param string $previousMode + * @param string $currentMode + * @dataProvider modesDataProvider + */ + public function testSwitchMode($previousMode, $currentMode) + { + $this->commandTester->execute( + [ + 'indexer' => 'catalog_product_price', + 'mode' => $currentMode, + ] + ); + $expectedOutput = 'Dimensions mode for indexer "Product Price" was changed from \'' + . $previousMode . '\' to \'' . $currentMode . '\'' . PHP_EOL; + + $actualOutput = $this->commandTester->getDisplay(); + + $this->assertContains($expectedOutput, $actualOutput); + + static::assertEquals( + Cli::RETURN_SUCCESS, + $this->commandTester->getStatusCode(), + $this->commandTester->getDisplay(true) + ); + } + + /** + * Modes data provider + * @return array + */ + public function modesDataProvider() + { + return [ + [DimensionModeConfiguration::DIMENSION_NONE, DimensionModeConfiguration::DIMENSION_WEBSITE], + [DimensionModeConfiguration::DIMENSION_WEBSITE, DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP], + [ + DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP, + DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP + ], + [ + DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP, + DimensionModeConfiguration::DIMENSION_NONE + ], + [ + DimensionModeConfiguration::DIMENSION_NONE, + DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP + ], + [ + DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP, + DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP + ], + [DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP, DimensionModeConfiguration::DIMENSION_WEBSITE], + [DimensionModeConfiguration::DIMENSION_WEBSITE, DimensionModeConfiguration::DIMENSION_NONE], + ]; + } + + /** + * @magentoAppArea adminhtml + * @magentoAppIsolation enabled + */ + public function testSwitchModeForSameMode() + { + $this->commandTester->execute( + [ + 'indexer' => 'catalog_product_price', + 'mode' => DimensionModeConfiguration::DIMENSION_NONE + ] + ); + $expectedOutput = 'Dimensions mode for indexer "Product Price" has not been changed' . PHP_EOL; + + $actualOutput = $this->commandTester->getDisplay(); + + $this->assertContains($expectedOutput, $actualOutput); + + static::assertEquals( + Cli::RETURN_SUCCESS, + $this->commandTester->getStatusCode(), + $this->commandTester->getDisplay(true) + ); + } + + /** + * @magentoAppArea adminhtml + * @magentoAppIsolation enabled + * + * @expectedException \InvalidArgumentException + */ + public function testSwitchModeWithInvalidArgument() + { + $this->commandTester->execute( + [ + 'indexer' => 'indexer_not_valid' + ] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php b/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php index b3707fc605017..9d1f584bfcd77 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php @@ -5,6 +5,11 @@ */ namespace Magento\Store\Block; +use Magento\Framework\App\ActionInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Url\DecoderInterface; +use Magento\Framework\App\ScopeInterface; + /** * Integration tests for \Magento\Store\Block\Switcher block. */ @@ -15,6 +20,11 @@ class SwitcherTest extends \PHPUnit\Framework\TestCase */ private $_objectManager; + /** + * @var DecoderInterface + */ + private $decoder; + /** * Set up. * @@ -22,11 +32,12 @@ class SwitcherTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->_objectManager = Bootstrap::getObjectManager(); + $this->decoder = Bootstrap::getObjectManager()->create(DecoderInterface::class); } /** - * Test that GetTargetStorePostData() method return correct store URL. + * Test that GetTargetStorePostData() method returns correct data. * * @magentoDataFixture Magento/Store/_files/store.php * @return void @@ -39,8 +50,16 @@ public function testGetTargetStorePostData() /** @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']); + $url = parse_url($this->decoder->decode($result['data'][ActionInterface::PARAM_NAME_URL_ENCODED])); + $storeParsedQuery = []; + if (isset($url['query'])) { + parse_str($url['query'], $storeParsedQuery); + } + + $this->assertSame($storeCode, $result['data']['___store']); + $this->assertSame($storeCode, $storeParsedQuery['___store']); + $this->assertSame(ScopeInterface::SCOPE_DEFAULT, $result['data']['___from_store']); } } diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreSwitcherTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreSwitcherTest.php new file mode 100644 index 0000000000000..0e4d129d17aa6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreSwitcherTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Model; + +use Magento\Framework\ObjectManagerInterface as ObjectManager; +use Magento\TestFramework\Helper\Bootstrap; + +class StoreSwitcherTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var StoreSwitcher + */ + private $storeSwitcher; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Class dependencies initialization + * + * @return void + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->storeSwitcher = $this->objectManager->get(StoreSwitcher::class); + } + + /** + * @magentoDataFixture Magento/Store/_files/store.php + * @magentoDataFixture Magento/Store/_files/second_store.php + * @return void + * @throws StoreSwitcher\CannotSwitchStoreException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSwitch(): void + { + $redirectUrl = "http://domain.com/?SID=e5h3e086dce3ckkqt9ia7avl27&___store=fixture_second_store"; + $expectedUrl = "http://domain.com/"; + $fromStoreCode = 'test'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $fromStore = $storeRepository->get($fromStoreCode); + + $toStoreCode = 'fixture_second_store'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $toStore = $storeRepository->get($toStoreCode); + + $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Swatches/_files/products_with_layered_navigation_swatch.php b/dev/tests/integration/testsuite/Magento/Swatches/_files/products_with_layered_navigation_swatch.php index e2df7206b27d9..215c32c4b521d 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/_files/products_with_layered_navigation_swatch.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/_files/products_with_layered_navigation_swatch.php @@ -123,7 +123,7 @@ function ($values, $index) use ($optionsPerAttribute) { ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('5.99') ->save(); @@ -148,7 +148,7 @@ function ($values, $index) use ($optionsPerAttribute) { ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('15.99') ->save(); @@ -168,7 +168,7 @@ function ($values, $index) use ($optionsPerAttribute) { ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCateroryIds([]) + ->setCategoryIds([]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('25.99') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php index a9a5c922c3d25..bd505fd4db035 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SetupUtil.php @@ -141,6 +141,7 @@ class SetupUtil 'discount_amount' => 40, 'discount_step' => 0, 'stop_rules_processing' => 1, + 'apply_to_shipping' => 0, 'website_ids' => [1], ]; diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php index b021040079538..ae1f475d43b71 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/SubtotalTest.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +// @codingStandardsIgnoreFile + namespace Magento\Tax\Model\Sales\Total\Quote; use Magento\TestFramework\Helper\Bootstrap; @@ -13,7 +15,7 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class SubtotalTest extends \PHPUnit\Framework\TestCase +class SubtotalTest extends \Magento\TestFramework\Indexer\TestCase { /** * Object Manager @@ -27,12 +29,23 @@ class SubtotalTest extends \PHPUnit\Framework\TestCase */ private $productRepository; + public static function setUpBeforeClass() + { + $db = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); + + parent::setUpBeforeClass(); + } + protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); - $this->productRepository = $this->objectManager->create( - \Magento\Catalog\Api\ProductRepositoryInterface::class - ); + $this->productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); } protected function getCustomerById($id) @@ -46,6 +59,7 @@ protected function getCustomerById($id) /** * @magentoAppIsolation enabled + * @magentoDbIsolation enabled * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Tax/_files/tax_classes.php @@ -61,8 +75,12 @@ public function testCollectUnitBased($expected) /** @var \Magento\Customer\Model\Customer $customer */ $customer = $this->objectManager->create(\Magento\Customer\Model\Customer::class)->load($fixtureCustomerId); /** @var \Magento\Customer\Model\Group $customerGroup */ - $customerGroup = $this->objectManager->create(\Magento\Customer\Model\Group::class) - ->load('custom_group', 'customer_group_code'); + $customerGroup = $this->objectManager->create( + \Magento\Customer\Model\Group::class + )->load( + 'custom_group', + 'customer_group_code' + ); $customerGroup->setTaxClassId($customerTaxClassId)->save(); $customer->setGroupId($customerGroup->getId())->save(); @@ -160,6 +178,7 @@ public function collectUnitBasedDataProvider() } /** + * @magentoDbIsolation disabled * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Tax/_files/tax_classes.php @@ -177,7 +196,10 @@ public function testCollectUnitBasedBundleProduct($expected) /** @var \Magento\Customer\Model\Group $customerGroup */ $customerGroup = $this->objectManager->create( \Magento\Customer\Model\Group::class - )->load('custom_group', 'customer_group_code'); + )->load( + 'custom_group', + 'customer_group_code' + ); $customerGroup->setTaxClassId($customerTaxClassId)->save(); $customer->setGroupId($customerGroup->getId())->save(); diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php index 9b498afc2500d..19343e49192ac 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php @@ -5,17 +5,19 @@ */ namespace Magento\Tax\Model\Sales\Total\Quote; +use Magento\Quote\Model\Quote\TotalsCollector; use Magento\Tax\Model\Calculation; use Magento\TestFramework\Helper\Bootstrap; require_once __DIR__ . '/SetupUtil.php'; require_once __DIR__ . '/../../../../_files/tax_calculation_data_aggregated.php'; +require_once __DIR__ . '/../../../../_files/full_discount_with_tax.php'; /** * Class TaxTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class TaxTest extends \PHPUnit\Framework\TestCase +class TaxTest extends \Magento\TestFramework\Indexer\TestCase { /** * Utility object for setting up tax rates, tax classes and tax rules @@ -24,6 +26,24 @@ class TaxTest extends \PHPUnit\Framework\TestCase */ protected $setupUtil = null; + /** + * @var TotalsCollector + */ + private $totalsCollector; + + /** + * test setup + */ + public function setUp() + { + /** @var \Magento\Framework\ObjectManagerInterface $objectManager */ + $objectManager = Bootstrap::getObjectManager(); + $this->totalsCollector = $objectManager->create(TotalsCollector::class); + $this->setupUtil = new SetupUtil($objectManager); + + parent::setUp(); + } + /** * Test taxes collection for quote. * @@ -108,6 +128,40 @@ public function testCollect() ); } + /** + * Test taxes collection with full discount for quote. + * + * Test tax calculation and price when the discount may be bigger than total + * This method will test the collector through $quote->collectTotals() method + * + * @see \Magento\SalesRule\Model\Utility::deltaRoundingFix + * @magentoDataFixture Magento/Tax/_files/full_discount_with_tax.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testFullDiscountWithDeltaRoundingFix() + { + global $fullDiscountIncTax; + $configData = $fullDiscountIncTax['config_data']; + $quoteData = $fullDiscountIncTax['quote_data']; + $expectedResults = $fullDiscountIncTax['expected_result']; + + /** @var \Magento\Framework\ObjectManagerInterface $objectManager */ + $objectManager = Bootstrap::getObjectManager(); + + //Setup tax configurations + $this->setupUtil = new SetupUtil($objectManager); + $this->setupUtil->setupTax($configData); + + $quote = $this->setupUtil->setupQuote($quoteData); + + $quote->collectTotals(); + + $quoteAddress = $quote->getShippingAddress(); + + $this->verifyResult($quoteAddress, $expectedResults); + } + /** * Verify fields in quote item * @@ -227,25 +281,26 @@ protected function verifyResult($quoteAddress, $expectedResults) * @param array $configData * @param array $quoteData * @param array $expectedResults - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @dataProvider taxDataProvider * @return void */ public function testTaxCalculation($configData, $quoteData, $expectedResults) { - /** @var \Magento\Framework\ObjectManagerInterface $objectManager */ - $objectManager = Bootstrap::getObjectManager(); - /** @var \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector */ - $totalsCollector = $objectManager->create(\Magento\Quote\Model\Quote\TotalsCollector::class); - + $db = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap() + ->getApplication() + ->getDbInstance(); + if (!$db->isDbDumpExists()) { + throw new \LogicException('DB dump does not exist.'); + } + $db->restoreFromDbDump(); //Setup tax configurations - $this->setupUtil = new SetupUtil($objectManager); $this->setupUtil->setupTax($configData); $quote = $this->setupUtil->setupQuote($quoteData); $quoteAddress = $quote->getShippingAddress(); - $totalsCollector->collectAddressTotals($quote, $quoteAddress); + $this->totalsCollector->collectAddressTotals($quote, $quoteAddress); $this->verifyResult($quoteAddress, $expectedResults); } diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php index e0bd6f3523612..dd5d12c5b9be3 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php @@ -2192,7 +2192,7 @@ protected function setupMultiRuleQuote() } /** - * Create the base results for the the multi rules test + * Create the base results for the multi rules test * * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxRuleRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxRuleRepositoryTest.php index cb45e1971ecab..0b98b9a519391 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxRuleRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxRuleRepositoryTest.php @@ -109,7 +109,7 @@ public function testSave() * @expectedExceptionMessage No such entity with taxRuleId = 9999 * @magentoDbIsolation enabled */ - public function testSaveThrowsExceptionIdTargetTaxRulDoesNotExist() + public function testSaveThrowsExceptionIdIfTargetTaxRuleDoesNotExist() { $taxRuleDataObject = $this->taxRuleFactory->create(); $taxRuleDataObject->setId(9999) diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/full_discount_with_tax.php b/dev/tests/integration/testsuite/Magento/Tax/_files/full_discount_with_tax.php new file mode 100644 index 0000000000000..2b5ef07de341a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/full_discount_with_tax.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Tax\Model\Config; +use Magento\Tax\Model\Sales\Total\Quote\SetupUtil; + +$fullDiscountIncTax = [ + 'config_data' => [ + 'config_overrides' => [ + Config::CONFIG_XML_PATH_APPLY_AFTER_DISCOUNT => 0, + Config::CONFIG_XML_PATH_DISCOUNT_TAX => 1, + Config::XML_PATH_ALGORITHM => 'ROW_BASE_CALCULATION', + Config::CONFIG_XML_PATH_SHIPPING_TAX_CLASS => SetupUtil::SHIPPING_TAX_CLASS, + ], + 'tax_rate_overrides' => [ + SetupUtil::TAX_RATE_TX => 18, + SetupUtil::TAX_RATE_SHIPPING => 0, + ], + 'tax_rule_overrides' => [ + [ + 'code' => 'Product Tax Rule', + 'product_tax_class_ids' => [ + SetupUtil::PRODUCT_TAX_CLASS_1 + ], + ], + [ + 'code' => 'Shipping Tax Rule', + 'product_tax_class_ids' => [ + SetupUtil::SHIPPING_TAX_CLASS + ], + 'tax_rate_ids' => [ + SetupUtil::TAX_RATE_SHIPPING, + ], + ], + ], + ], + 'quote_data' => [ + 'billing_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'shipping_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'items' => [ + [ + 'sku' => 'simple1', + 'price' => 2542.37, + 'qty' => 2, + ] + ], + 'shipping_method' => 'free', + 'shopping_cart_rules' => [ + [ + 'discount_amount' => 100 + ], + ], + ], + 'expected_result' => [ + 'address_data' => [ + 'subtotal' => 5084.74, + 'base_subtotal' => 5084.74, + 'subtotal_incl_tax' => 5999.99, + 'base_subtotal_incl_tax' => 5999.99, + 'tax_amount' => 915.25, + 'base_tax_amount' => 915.25, + 'shipping_amount' => 0, + 'base_shipping_amount' => 0, + 'shipping_incl_tax' => 0, + 'base_shipping_incl_tax' => 0, + 'shipping_tax_amount' => 0, + 'base_shipping_tax_amount' => 0, + 'discount_amount' => -5999.99, + 'base_discount_amount' => -5999.99, + 'discount_tax_compensation_amount' => 0, + 'base_discount_tax_compensation_amount' => 0, + 'shipping_discount_tax_compensation_amount' => 0, + 'base_shipping_discount_tax_compensation_amount' => 0, + 'grand_total' => 0, + 'base_grand_total' => 0, + 'applied_taxes' => [ + SetupUtil::TAX_RATE_TX => [ + 'percent' => 18, + 'amount' => 915.25, + 'base_amount' => 915.25, + 'rates' => [ + [ + 'code' => SetupUtil::TAX_RATE_TX, + 'title' => SetupUtil::TAX_RATE_TX, + 'percent' => 18, + ], + ], + ] + ], + ], + 'items_data' => [ + 'simple1' => [ + 'row_total' => 5084.74, + 'base_row_total' => 5084.74, + 'tax_percent' => 18, + 'price' => 2542.37, + 'base_price' => 2542.37, + 'price_incl_tax' => 3000, + 'base_price_incl_tax' => 3000, + 'row_total_incl_tax' => 5999.99, + 'base_row_total_incl_tax' => 5999.99, + 'tax_amount' => 915.25, + 'base_tax_amount' => 915.25, + 'discount_amount' => 5999.99, + 'base_discount_amount' => 5999.99, + 'discount_percent' => 100, + 'discount_tax_compensation_amount' => 0, + 'base_discount_tax_compensation_amount' => 0, + ], + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_apply_tax_after_discount.php b/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_apply_tax_after_discount.php new file mode 100644 index 0000000000000..e0cb82e50cbd4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/scenarios/including_tax_apply_tax_after_discount.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Tax\Model\Calculation; +use Magento\Tax\Model\Config; +use Magento\Tax\Model\Sales\Total\Quote\SetupUtil; + +$taxCalculationData['including_tax_apply_tax_after_discount'] = [ + 'config_data' => [ + SetupUtil::CONFIG_OVERRIDES => [ + Config::CONFIG_XML_PATH_APPLY_AFTER_DISCOUNT => 1, + Config::CONFIG_XML_PATH_PRICE_INCLUDES_TAX => 1, + Config::CONFIG_XML_PATH_SHIPPING_INCLUDES_TAX => 1, + Config::CONFIG_XML_PATH_SHIPPING_TAX_CLASS => SetupUtil::SHIPPING_TAX_CLASS, + Config::XML_PATH_ALGORITHM => Calculation::CALC_ROW_BASE, + ], + SetupUtil::TAX_RATE_OVERRIDES => [ + SetupUtil::TAX_RATE_TX => 10, + SetupUtil::TAX_STORE_RATE => 10, + SetupUtil::TAX_RATE_SHIPPING => 10, + ], + SetupUtil::TAX_RULE_OVERRIDES => [ + [ + //tax rule for product + 'code' => 'Product Tax Rule', + 'product_tax_class_ids' => [SetupUtil::PRODUCT_TAX_CLASS_1], + ], + [ + //tax rule for shipping + 'code' => 'Shipping Tax Rule', + 'product_tax_class_ids' => [SetupUtil::SHIPPING_TAX_CLASS], + 'tax_rate_ids' => [SetupUtil::TAX_RATE_SHIPPING], + ], + ], + ], + 'quote_data' => [ + 'billing_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'shipping_address' => [ + 'region_id' => SetupUtil::REGION_TX, + ], + 'items' => [ + [ + 'sku' => 'simple1', + 'price' => 29, + 'qty' => 1, + ], + ], + 'shipping_method' => 'flatrate_flatrate', + 'shopping_cart_rules' => [ + [ + 'discount_amount' => 50, + 'apply_to_shipping' => 1, + ], + ], + ], + 'expected_results' => [ + 'address_data' => [ + 'subtotal' => 26.36, + 'base_subtotal' => 26.36, + 'subtotal_incl_tax' => 29, + 'base_subtotal_incl_tax' => 29, + 'tax_amount' => 1.69, + 'base_tax_amount' => 1.69, + 'shipping_amount' => 4.55, + 'base_shipping_amount' => 4.55, + 'shipping_incl_tax' => 5, + 'base_shipping_incl_tax' => 5, + 'shipping_tax_amount' => 0.25, + 'base_shipping_tax_amount' => 0.25, + 'discount_amount' => -15.455, + 'base_discount_amount' => -15.455, + 'discount_tax_compensation_amount' => 1.2, + 'base_discount_tax_compensation_amount' => 1.2, + 'shipping_discount_tax_compensation_amount' => 0.2, + 'base_shipping_discount_tax_compensation_amount' => 0.2, + 'grand_total' => 18.545, + 'base_grand_total' => 18.545, + 'applied_taxes' => [ + SetupUtil::TAX_RATE_TX => [ + 'percent' => 10, + 'amount' => 1.44, + 'base_amount' => 1.44, + 'rates' => [ + [ + 'code' => SetupUtil::TAX_RATE_TX, + 'title' => SetupUtil::TAX_RATE_TX, + 'percent' => 10, + ], + ], + ], + SetupUtil::TAX_RATE_SHIPPING => [ + 'percent' => 10, + 'amount' => 0.25, + 'base_amount' => 0.25, + 'rates' => [ + [ + 'code' => SetupUtil::TAX_RATE_SHIPPING, + 'title' => SetupUtil::TAX_RATE_SHIPPING, + 'percent' => 10, + ], + ], + ], + ], + ], + 'items_data' => [ + 'simple1' => [ + 'row_total' => 26.36, + 'base_row_total' => 26.36, + 'tax_percent' => 10, + 'price' => 26.36, + 'base_price' => 26.36, + 'price_incl_tax' => 29, + 'base_price_incl_tax' => 29, + 'row_total_incl_tax' => 29, + 'base_row_total_incl_tax' => 29, + 'tax_amount' => 1.44, + 'base_tax_amount' => 1.44, + 'discount_amount' => 13.18, + 'base_discount_amount' => 13.18, + 'discount_percent' => 50, + 'discount_tax_compensation_amount' => 1.2, + 'base_discount_tax_compensation_amount' => 1.2, + ], + ], + ], +]; diff --git a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php index c47d348cedda5..f22b48a259685 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php +++ b/dev/tests/integration/testsuite/Magento/Tax/_files/tax_calculation_data_aggregated.php @@ -30,3 +30,4 @@ require_once __DIR__ . '/scenarios/multi_tax_rule_total_calculate_subtotal_yes.php'; require_once __DIR__ . '/scenarios/multi_tax_rule_two_row_calculate_subtotal_yes_row.php'; require_once __DIR__ . '/scenarios/multi_tax_rule_two_row_calculate_subtotal_yes_total.php'; +require_once __DIR__ . '/scenarios/including_tax_apply_tax_after_discount.php'; diff --git a/dev/tests/integration/testsuite/Magento/TaxImportExport/Model/Rate/CsvImportHandlerTest.php b/dev/tests/integration/testsuite/Magento/TaxImportExport/Model/Rate/CsvImportHandlerTest.php index ed7613f3dd106..6c2c291e6c295 100644 --- a/dev/tests/integration/testsuite/Magento/TaxImportExport/Model/Rate/CsvImportHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/TaxImportExport/Model/Rate/CsvImportHandlerTest.php @@ -57,7 +57,7 @@ public function testImportFromCsvFileWithCorrectData() /** * @magentoDbIsolation enabled * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage One of the countries has invalid code. + * @expectedExceptionMessage Country code is invalid: ZZ */ public function testImportFromCsvFileThrowsExceptionWhenCountryCodeIsInvalid() { diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index a9f6752c9de82..aa301e8d3e423 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -5,20 +5,27 @@ */ namespace Magento\Ups\Model; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Quote\Model\Quote\Address\RateRequestFactory; + class CarrierTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Ups\Model\Carrier + * @var Carrier */ private $carrier; + /** + * @return void + */ protected function setUp() { - $this->carrier = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Ups\Model\Carrier::class - ); + $this->carrier = Bootstrap::getObjectManager()->create(Carrier::class); } + /** + * @return void + */ public function testGetShipAcceptUrl() { $this->assertEquals('https://wwwcie.ups.com/ups.app/xml/ShipAccept', $this->carrier->getShipAcceptUrl()); @@ -34,6 +41,9 @@ public function testGetShipAcceptUrlLive() $this->assertEquals('https://onlinetools.ups.com/ups.app/xml/ShipAccept', $this->carrier->getShipAcceptUrl()); } + /** + * @return void + */ public function testGetShipConfirmUrl() { $this->assertEquals('https://wwwcie.ups.com/ups.app/xml/ShipConfirm', $this->carrier->getShipConfirmUrl()); @@ -51,4 +61,27 @@ public function testGetShipConfirmUrlLive() $this->carrier->getShipConfirmUrl() ); } + + /** + * @magentoConfigFixture current_store carriers/ups/active 1 + * @magentoConfigFixture current_store carriers/ups/allowed_methods 1DA,GND + * @magentoConfigFixture current_store carriers/ups/free_method GND + */ + public function testCollectFreeRates() + { + $rateRequest = Bootstrap::getObjectManager()->get(RateRequestFactory::class)->create(); + $rateRequest->setDestCountryId('US'); + $rateRequest->setDestRegionId('CA'); + $rateRequest->setDestPostcode('90001'); + $rateRequest->setPackageQty(1); + $rateRequest->setPackageWeight(1); + $rateRequest->setFreeMethodWeight(0); + $rateRequest->setLimitCarrier($this->carrier::CODE); + $rateRequest->setFreeShipping(true); + $rateResult = $this->carrier->collectRates($rateRequest); + $result = $rateResult->asArray(); + $methods = $result[$this->carrier::CODE]['methods']; + $this->assertEquals(0, $methods['GND']['price']); + $this->assertNotEquals(0, $methods['1DA']['price']); + } } diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php index 1ebbb35dd7dc4..5593cecb10d91 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php @@ -42,6 +42,9 @@ public function testMatchUrlRewrite( ); } + /** + * @return array + */ public function requestDataProvider() { return [ @@ -69,12 +72,6 @@ public function requestDataProvider() 'request' => '/page-similar/', 'redirect' => '/page-b', ], - 'Use Case #7: Rewrite during stores switching' => [ - 'request' => '/page-c-on-2nd-store' - . '?___store=default&___from_store=fixture_second_store', - 'redirect' => '/page-c-on-1st-store', - 'expectedCode' => 302 - ], ]; } } diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php new file mode 100644 index 0000000000000..2cb86358667c0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\UrlRewrite\Model\StoreSwitcher; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\Value; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreSwitcher; +use Magento\Framework\ObjectManagerInterface as ObjectManager; +use Magento\TestFramework\Helper\Bootstrap; + +class RewriteUrlTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var StoreSwitcher + */ + private $storeSwitcher; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * Class dependencies initialization + * + * @return void + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->storeSwitcher = $this->objectManager->get(StoreSwitcher::class); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/UrlRewrite/_files/url_rewrite.php + * @magentoDataFixture Magento/Catalog/_files/category_product.php + * @return void + * @throws StoreSwitcher\CannotSwitchStoreException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSwitchToNonExistingPage(): void + { + $fromStoreCode = 'default'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $fromStore = $storeRepository->get($fromStoreCode); + + $toStoreCode = 'fixture_second_store'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $toStore = $storeRepository->get($toStoreCode); + + $this->setBaseUrl($toStore); + + $product = $this->productRepository->get('simple333'); + + $redirectUrl = "http://domain.com/{$product->getUrlKey()}.html"; + $expectedUrl = $toStore->getBaseUrl(); + + $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); + } + + /** + * @magentoDataFixture Magento/UrlRewrite/_files/url_rewrite.php + * @return void + * @throws StoreSwitcher\CannotSwitchStoreException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function testSwitchToExistingPage(): void + { + $fromStoreCode = 'default'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $fromStore = $storeRepository->get($fromStoreCode); + + $toStoreCode = 'fixture_second_store'; + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $toStore = $storeRepository->get($toStoreCode); + + $redirectUrl = $expectedUrl = "http://localhost/page-c"; + + $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); + } + + /** + * Set base url to store. + * + * @param StoreInterface $targetStore + * @return void + */ + private function setBaseUrl(StoreInterface $targetStore): void + { + $configValue = $this->objectManager->create(Value::class); + $configValue->load('web/unsecure/base_url', 'path'); + $baseUrl = 'http://domain.com/'; + if (!$configValue->getPath()) { + $configValue->setPath('web/unsecure/base_url'); + } + $configValue->setValue($baseUrl); + $configValue->setScope(ScopeInterface::SCOPE_STORES); + $configValue->setScopeId($targetStore->getId()); + $configValue->save(); + + $reinitibleConfig = $this->objectManager->create(ReinitableConfigInterface::class); + $reinitibleConfig->reinit(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php new file mode 100644 index 0000000000000..19cce769b1c19 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +/** @var \Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection $urlRewriteCollection */ +$urlRewriteCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection::class); +$collection = $urlRewriteCollection + ->addFieldToFilter('entity_type', 'custom') + ->addFieldToFilter('request_path', ['page-a', 'page-b', 'page-c']) + ->addFieldToFilter('entity_id', '333') + ->load() + ->walk('delete'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Usps/Api/GuestCouponManagementTest.php b/dev/tests/integration/testsuite/Magento/Usps/Api/GuestCouponManagementTest.php new file mode 100644 index 0000000000000..c4656a4801eaa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Usps/Api/GuestCouponManagementTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Usps\Api; + +use Magento\Catalog\Model\Product\Type; +use Magento\Framework\DataObject; +use Magento\Framework\HTTP\ZendClient; +use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Quote\Api\Data\CartItemInterfaceFactory; +use Magento\Quote\Api\Data\ShippingMethodInterface; +use Magento\Quote\Api\GuestCartItemRepositoryInterface; +use Magento\Quote\Api\GuestCartManagementInterface; +use Magento\Quote\Api\GuestCouponManagementInterface; +use Magento\Quote\Api\GuestShipmentEstimationInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GuestCouponManagementTest extends TestCase +{ + /** + * @var GuestCouponManagementInterface + */ + private $management; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ZendClient|MockObject + */ + private $httpClient; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->management = $this->objectManager->get(GuestCouponManagementInterface::class); + $clientFactory = $this->getMockBuilder(ZendClientFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->httpClient = $this->getMockBuilder(ZendClient::class) + ->disableOriginalConstructor() + ->getMock(); + $clientFactory->method('create') + ->willReturn($this->httpClient); + + $this->objectManager->addSharedInstance($clientFactory, ZendClientFactory::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(ZendClientFactory::class); + } + + /** + * Checks a case when coupon is applied for a guest cart and USPS Priority Mail 1-Day configured as free method. + * + * @magentoConfigFixture default_store carriers/usps/active 1 + * @magentoConfigFixture default_store carriers/usps/free_method 1 + * @magentoDataFixture Magento/Usps/Fixtures/cart_rule_coupon_free_shipping.php + * @magentoDataFixture Magento/Quote/_files/is_salable_product.php + * @return void + */ + public function testFreeShippingWithCoupon(): void + { + $couponCode = 'IMPHBR852R61'; + $cartId = $this->createGuestCart(); + + $request = new DataObject(['body' => file_get_contents(__DIR__ . '/../Fixtures/rates_response.xml')]); + $this->httpClient->method('request') + ->willReturn($request); + + self::assertTrue($this->management->set($cartId, $couponCode)); + + $methods = $this->estimateShipping($cartId); + $methods = $this->filterFreeShippingMethods($methods); + self::assertEquals(['Fixed', 'Priority Mail 1-Day'], $methods); + } + + /** + * Creates guest shopping cart. + * + * @return string + */ + private function createGuestCart(): string + { + /** @var GuestCartManagementInterface $cartManagement */ + $cartManagement = $this->objectManager->get(GuestCartManagementInterface::class); + $cartId = $cartManagement->createEmptyCart(); + + /** @var CartItemInterfaceFactory $cartItemFactory */ + $cartItemFactory = $this->objectManager->get(CartItemInterfaceFactory::class); + + /** @var CartItemInterface $cartItem */ + $cartItem = $cartItemFactory->create(); + $cartItem->setQuoteId($cartId); + $cartItem->setQty(1); + $cartItem->setSku('simple-99'); + $cartItem->setProductType(Type::TYPE_SIMPLE); + + /** @var GuestCartItemRepositoryInterface $itemRepository */ + $itemRepository = $this->objectManager->get(GuestCartItemRepositoryInterface::class); + $itemRepository->save($cartItem); + + return $cartId; + } + + /** + * Estimates shipment for guest cart. + * + * @param string $cartId + * @return array ShippingMethodInterface[] + */ + private function estimateShipping(string $cartId): array + { + $addressFactory = $this->objectManager->get(AddressInterfaceFactory::class); + /** @var AddressInterface $address */ + $address = $addressFactory->create(); + $address->setCountryId('US'); + $address->setRegionId(12); + $address->setPostcode(90230); + + /** @var GuestShipmentEstimationInterface $estimation */ + $estimation = $this->objectManager->get(GuestShipmentEstimationInterface::class); + return $estimation->estimateByExtendedAddress($cartId, $address); + } + + /** + * Filters free shipping methods. + * + * @param array $methods + * @return array + */ + private function filterFreeShippingMethods(array $methods): array + { + $result = []; + /** @var ShippingMethodInterface $method */ + foreach ($methods as $method) { + if ($method->getAmount() == 0) { + $result[] = $method->getMethodTitle(); + } + } + return $result; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping.php b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping.php new file mode 100644 index 0000000000000..8d140593677ec --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\SalesRule\Api\CouponRepositoryInterface; +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** + * @var Rule $salesRule + * @var Registry $registry + */ +require __DIR__ . '/../../SalesRule/_files/cart_rule_free_shipping.php'; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$salesRule->setCouponType(Rule::COUPON_TYPE_SPECIFIC)->setUseAutoGeneration(0); +$salesRule->save(); + +$couponCode = 'IMPHBR852R61'; +$coupon = $objectManager->create(Coupon::class); +$coupon->setRuleId($salesRule->getId()) + ->setCode($couponCode) + ->setType(0); +$objectManager->get(CouponRepositoryInterface::class) + ->save($coupon); + +$registry->unregister('cart_rule_free_shipping'); +$registry->register('cart_rule_free_shipping', $salesRule); diff --git a/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping_rollback.php b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping_rollback.php new file mode 100644 index 0000000000000..17b37e3d024fd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/cart_rule_coupon_free_shipping_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../SalesRule/_files/cart_rule_free_shipping_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Usps/Fixtures/rates_response.xml b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/rates_response.xml new file mode 100644 index 0000000000000..22cf7035e4eae --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Usps/Fixtures/rates_response.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<RateV4Response> + <Package ID="0"> + <ZipOrigination>90034</ZipOrigination> + <ZipDestination>90230</ZipDestination> + <Pounds>1</Pounds> + <Ounces>0.00000000</Ounces> + <Size>REGULAR</Size> + <Machinable>TRUE</Machinable> + <Zone>1</Zone> + <Postage CLASSID="3"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt;</MailService> + <Rate>24.70</Rate> + </Postage> + <Postage CLASSID="2"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Hold For Pickup</MailService> + <Rate>24.70</Rate> + </Postage> + <Postage CLASSID="13"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Flat Rate Envelope</MailService> + <Rate>24.70</Rate> + </Postage> + <Postage CLASSID="27"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Flat Rate Envelope Hold For Pickup</MailService> + <Rate>24.70</Rate> + </Postage> + <Postage CLASSID="30"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Legal Flat Rate Envelope</MailService> + <Rate>24.90</Rate> + </Postage> + <Postage CLASSID="31"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Legal Flat Rate Envelope Hold For Pickup</MailService> + <Rate>24.90</Rate> + </Postage> + <Postage CLASSID="62"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Padded Flat Rate Envelope</MailService> + <Rate>25.40</Rate> + </Postage> + <Postage CLASSID="63"> + <MailService>Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Padded Flat Rate Envelope Hold For Pickup</MailService> + <Rate>25.40</Rate> + </Postage> + <Postage CLASSID="1"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt;</MailService> + <Rate>6.70</Rate> + </Postage> + <Postage CLASSID="22"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Large Flat Rate Box</MailService> + <Rate>18.90</Rate> + </Postage> + <Postage CLASSID="17"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Medium Flat Rate Box</MailService> + <Rate>13.65</Rate> + </Postage> + <Postage CLASSID="28"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Small Flat Rate Box</MailService> + <Rate>7.20</Rate> + </Postage> + <Postage CLASSID="16"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Flat Rate Envelope</MailService> + <Rate>6.70</Rate> + </Postage> + <Postage CLASSID="44"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Legal Flat Rate Envelope</MailService> + <Rate>7.00</Rate> + </Postage> + <Postage CLASSID="29"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Padded Flat Rate Envelope</MailService> + <Rate>7.25</Rate> + </Postage> + <Postage CLASSID="38"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Gift Card Flat Rate Envelope</MailService> + <Rate>6.70</Rate> + </Postage> + <Postage CLASSID="42"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Small Flat Rate Envelope</MailService> + <Rate>6.70</Rate> + </Postage> + <Postage CLASSID="40"> + <MailService>Priority Mail 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Window Flat Rate Envelope</MailService> + <Rate>6.70</Rate> + </Postage> + <Postage CLASSID="6"> + <MailService>Media Mail Parcel</MailService> + <Rate>2.66</Rate> + </Postage> + <Postage CLASSID="7"> + <MailService>Library Mail Parcel</MailService> + <Rate>2.53</Rate> + </Postage> + </Package> +</RateV4Response> \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Vault/_files/token.php b/dev/tests/integration/testsuite/Magento/Vault/_files/token.php index 396b2a5c20536..e1e0a31da4f5f 100644 --- a/dev/tests/integration/testsuite/Magento/Vault/_files/token.php +++ b/dev/tests/integration/testsuite/Magento/Vault/_files/token.php @@ -23,4 +23,4 @@ /** @var PaymentTokenRepository $tokenRepository */ $tokenRepository = $objectManager->create(PaymentTokenRepository::class); -$tokenRepository->save($token); +$token = $tokenRepository->save($token); diff --git a/dev/tests/integration/testsuite/Magento/Widget/Setup/LayoutUpdateConverterTest.php b/dev/tests/integration/testsuite/Magento/Widget/Setup/LayoutUpdateConverterTest.php index 083876d7eddb3..43c0bd202cc92 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/Setup/LayoutUpdateConverterTest.php +++ b/dev/tests/integration/testsuite/Magento/Widget/Setup/LayoutUpdateConverterTest.php @@ -32,7 +32,7 @@ public function testConvert($value, $expected) public function convertDataProvider() { // @codingStandardsIgnoreStart - $beginning = '<body><referenceContainer name="content"><block class="Magento\CatalogWidget\Block\Product\ProductsList" name="23e38bbfa7cc6474454570e51aeffcc3" template="product/widget/content/grid.phtml"><action method="setData"><argument name="name" xsi:type="string">show_pager</argument><argument name="value" xsi:type="string">0</argument></action><action method="setData"><argument name="name" xsi:type="string">products_count</argument><argument name="value" xsi:type="string">10</argument></action><action method="setData">'; + $beginning = '<body><referenceContainer name="content"><block class="Magento\CatalogWidget\Block\Product\ProductsList" name="23e38bbfa7cc6474454570e51aeffcc3" template="Magento_CatalogWidget::product/widget/content/grid.phtml"><action method="setData"><argument name="name" xsi:type="string">show_pager</argument><argument name="value" xsi:type="string">0</argument></action><action method="setData"><argument name="name" xsi:type="string">products_count</argument><argument name="value" xsi:type="string">10</argument></action><action method="setData">'; $serializedWidgetXml = '<argument name="name" xsi:type="string">conditions_encoded</argument><argument name="value" xsi:type="string">a:3:[i:1;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Combine`;s:10:`aggregator`;s:3:`all`;s:5:`value`;s:1:`1`;s:9:`new_child`;s:0:``;]s:4:`1--1`;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Product`;s:9:`attribute`;s:3:`sku`;s:8:`operator`;s:2:`()`;s:5:`value`;s:15:`simple, simple1`;]s:4:`1--2`;a:4:[s:4:`type`;s:50:`Magento|CatalogWidget|Model|Rule|Condition|Product`;s:9:`attribute`;s:5:`price`;s:8:`operator`;s:2:`<=`;s:5:`value`;s:2:`10`;]]</argument>'; $jsonEncodedWidgetXml = '<argument name="name" xsi:type="string">conditions_encoded</argument><argument name="value" xsi:type="string">^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,`aggregator`:`all`,`value`:`1`,`new_child`:``^],`1--1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,`attribute`:`sku`,`operator`:`()`,`value`:`simple, simple1`^],`1--2`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,`attribute`:`price`,`operator`:`<=`,`value`:`10`^]^]</argument>'; $ending = '</action><action method="setData"><argument name="name" xsi:type="string">page_var_name</argument><argument name="value" xsi:type="string">pobqks</argument></action></block></referenceContainer></body>'; diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php index 4c183ad6368cc..73ced4d5462cc 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php @@ -12,14 +12,4 @@ ); $wishlist->loadByCustomerId($customer->getId(), true); $item = $wishlist->addNewItem($product, new \Magento\Framework\DataObject([])); -// 'product' => '1', -// 'related_product' => '', -// 'options' => array( -// 1 => '1-text', -// 2 => array('month' => 1, 'day' => 1, 'year' => 2001, 'hour' => 1, 'minute' => 1), -// 3 => '1', -// 4 => '1', -// ), -// 'validate_datetime_2' => '', -// 'qty' => '1', $wishlist->setSharingCode('fixture_unique_code')->save(); 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 index 596017be9a33c..6fabc513da9af 100644 --- 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 @@ -27,7 +27,24 @@ define([ }) }, 'Magento_Braintree/js/view/payment/adapter': { + config: {}, + + /** Stub */ + onReady: function () {}, + + /** Stub */ + setConfig: function (config) { + this.config = config; + }, + + /** Stub */ + setup: function () { + this.config.onReady(this.checkout); + }, + checkout: { + /** Stub */ + teardown: function () {}, paypal: { /** Stub */ initAuthFlow: function () {} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/use-parent-settings/toggle-disabled-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/use-parent-settings/toggle-disabled-mixin.test.js new file mode 100644 index 0000000000000..f3e53673a4e59 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/adminhtml/js/use-parent-settings/toggle-disabled-mixin.test.js @@ -0,0 +1,141 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*eslint max-nested-callbacks: 0*/ +define([ + 'underscore', + 'Magento_Catalog/js/components/use-parent-settings/select', + 'Magento_Catalog/js/components/use-parent-settings/toggle-disabled-mixin' +], function (_, select, mixin) { + 'use strict'; + + var CustomInput = mixin(select), + defaultProperties = { + name: 'uiSelect', + dataScope: '', + provider: 'provider', + service: true + }, + obj; + + describe('toggle-disabled-mixin structure tests', function () { + var defaultContext = require.s.contexts._; + + obj = new CustomInput(defaultProperties); + + it('mixin is present in RequireJs config', function () { + var requireJsConfig = defaultContext.config + .config.mixins['Magento_Catalog/js/components/use-parent-settings/select']; + + expect( + requireJsConfig['Magento_Catalog/js/components/use-parent-settings/toggle-disabled-mixin'] + ).toBe(true); + }); + + it('Check for useParent property', function () { + expect(obj.hasOwnProperty('useParent')).toBeTruthy(); + expect(typeof obj.useParent).toEqual('boolean'); + expect(obj.useParent).toEqual(false); + }); + + it('Check for useDefaults property', function () { + expect(obj.hasOwnProperty('useDefaults')).toBeTruthy(); + expect(typeof obj.useDefaults).toEqual('boolean'); + expect(obj.useDefaults).toEqual(false); + }); + + it('Check for toggleDisabled method', function () { + expect(obj.toggleDisabled).toBeDefined(); + expect(typeof obj.toggleDisabled).toEqual('function'); + }); + + it('Check for saveUseDefaults method', function () { + expect(obj.saveUseDefaults).toBeDefined(); + expect(typeof obj.saveUseDefaults).toEqual('function'); + }); + + it('Check for setInitialValue method', function () { + expect(obj.setInitialValue).toBeDefined(); + expect(typeof obj.setInitialValue).toEqual('function'); + }); + + it('Check for toggleUseDefault method', function () { + expect(obj.toggleUseDefault).toBeDefined(); + expect(typeof obj.toggleUseDefault).toEqual('function'); + }); + }); + + describe('toggle-disabled-mixin functionality', function () { + var dataProvider = [ + { + defaults: { + useParent: false, + useDefaults: false + }, + expected: { + disabled: false + } + }, + { + defaults: { + useParent: true, + useDefaults: false + }, + expected: { + disabled: true + } + }, + { + defaults: { + useParent: false, + useDefaults: true + }, + expected: { + disabled: true + } + }, + { + defaults: { + useParent: true, + useDefaults: true + }, + expected: { + disabled: true + } + } + ]; + + dataProvider.forEach(function (state) { + describe(JSON.stringify(state.defaults), function () { + + beforeEach(function () { + obj = new CustomInput( + _.extend(defaultProperties, state.defaults) + ); + }); + + it('Check disabled state', function () { + expect(obj.disabled()).toEqual(state.expected.disabled); + }); + + it('Check checked state', function () { + expect(obj.isUseDefault()).toEqual(state.defaults.useDefaults); + }); + + it('Check of using parent settings', function () { + obj.toggleDisabled(true); + expect(obj.isUseDefault()).toEqual(state.defaults.useDefaults); + expect(obj.disabled()).toEqual(true); + }); + + it('Check of using self settings', function () { + obj.toggleDisabled(false); + expect(obj.isUseDefault()).toEqual(state.defaults.useDefaults); + expect(obj.disabled()).toEqual(obj.isUseDefault()); + }); + }); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js index 4ef1aa2a98976..1a3a5726080bc 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Catalog/frontend/js/product/storage/storage-service.test.js @@ -12,22 +12,11 @@ define([ var injector = new Squire(), mocks = { - 'Magento_Catalog/js/product/storage/ids-storage': { - name: 'IdsStorage', - initialize: jasmine.createSpy().and.returnValue({}) - }, 'Magento_Catalog/js/product/storage/data-storage': {}, 'Magento_Catalog/js/product/storage/ids-storage-compare': {} }, - obj; - - beforeEach(function (done) { - injector.mock(mocks); - injector.require(['Magento_Catalog/js/product/storage/storage-service'], function (insance) { - obj = insance; - done(); - }); - }); + obj, + utils; afterEach(function () { try { @@ -43,6 +32,19 @@ define([ }, storage; + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Catalog/js/product/storage/ids-storage', + 'Magento_Catalog/js/product/storage/storage-service', + 'mageUtils' + ], function (IdsStorage, instance, mageUtils) { + obj = instance; + utils = mageUtils; + done(); + }); + }); + describe('"createStorage" method', function () { it('create new storage', function () { obj.processSubscribers = jasmine.createSpy(); @@ -73,5 +75,54 @@ define([ expect(typeof obj.getStorage(config.namespace)).toBe('object'); }); }); + describe('"add" method', function () { + var storageValue; + + beforeEach(function () { + storage = new obj.createStorage(config); + storageValue = { + 'property1': 1 + }; + + storage.set(storageValue); + }); + + it('method exists', function () { + expect(storage.add).toBeDefined(); + expect(typeof storage.add).toEqual('function'); + }); + + it('update value', function () { + spyOn(utils, 'copy').and.callThrough(); + expect(storage.get()).toEqual(storageValue); + + storageValue = { + 'property2': 2 + }; + + storage.add(storageValue); + + expect(utils.copy).toHaveBeenCalled(); + expect(storage.get()).toEqual( + { + 'property1': 1, + 'property2': 2 + } + ); + }); + + it('add empty value', function () { + spyOn(utils, 'copy').and.callThrough(); + + storage.add({}); + + expect(utils.copy).not.toHaveBeenCalled(); + expect(storage.get()).toEqual( + { + 'property1': 1 + } + ); + }); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js index 47518bef19e56..184118f1d02ec 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/totals-processor/default.test.js @@ -113,6 +113,8 @@ define([ }); it('estimateTotals if data wasn\'t cached and request was successfully sent', function () { + var deferral = new $.Deferred(); + spyOn(mocks['Magento_Checkout/js/model/cart/cache'], 'isChanged').and.returnValue(true); spyOn(mocks['Magento_Customer/js/customer-data'], 'get').and.returnValue( ko.observable({ @@ -124,9 +126,9 @@ define([ data.shippingMethodCode = mocks['Magento_Checkout/js/model/quote'].shippingMethod()['method_code']; data.shippingCarrierCode = mocks['Magento_Checkout/js/model/quote'].shippingMethod()['carrier_code']; - return new $.Deferred().resolve(result); + return deferral.resolve(result); }); - expect(defaultProcessor.estimateTotals(address)).toBeUndefined(); + expect(defaultProcessor.estimateTotals(address)).toBe(deferral); expect(mocks['Magento_Checkout/js/model/quote'].setTotals).toHaveBeenCalledWith(totals); expect(mocks['Magento_Checkout/js/model/totals'].isLoading.calls.argsFor(0)[0]).toBe(true); expect(mocks['Magento_Checkout/js/model/totals'].isLoading.calls.argsFor(1)[0]).toBe(false); @@ -136,6 +138,8 @@ define([ }); it('estimateTotals if data wasn\'t cached and request returns error', function () { + var deferral = new $.Deferred(); + spyOn(mocks['Magento_Checkout/js/model/cart/cache'], 'isChanged').and.returnValue(true); spyOn(mocks['Magento_Customer/js/customer-data'], 'get').and.returnValue( ko.observable({ @@ -144,9 +148,9 @@ define([ ); spyOn(mocks['Magento_Checkout/js/model/cart/cache'], 'get'); spyOn(mocks['mage/storage'], 'post').and.callFake(function () { - return new $.Deferred().reject('Error Message'); + return deferral.reject('Error Message'); }); - expect(defaultProcessor.estimateTotals(address)).toBeUndefined(); + expect(defaultProcessor.estimateTotals(address)).toBe(deferral); expect(mocks['Magento_Checkout/js/model/totals'].isLoading.calls.argsFor(0)[0]).toBe(true); expect(mocks['Magento_Checkout/js/model/totals'].isLoading.calls.argsFor(1)[0]).toBe(false); expect(mocks['mage/storage'].post).toHaveBeenCalled(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/sidebar.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/sidebar.test.js new file mode 100644 index 0000000000000..8b4fae5a69461 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/sidebar.test.js @@ -0,0 +1,109 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/* eslint-disable max-nested-callbacks */ +/*jscs:disable jsDoc*/ + +define([ + 'squire', + 'jquery', + 'ko' +], function (Squire, $, ko) { + 'use strict'; + + describe('Magento_Checkout/js/sidebar', function () { + var injector = new Squire(), + mocks = { + 'Magento_Customer/js/customer-data': { + get: function () { + return ko.observable(); + } + } + }, + sidebar, + cartData = { + 'items': [ + { + 'item_id': 1, + 'product_sku': 'bundle' + }, + { + 'item_id': 5, + 'product_sku': 'simple' + }, + { + 'item_id': 7, + 'product_sku': 'configurable' + } + ] + }, + cart = ko.observable(cartData); + + beforeEach(function (done) { + injector.mock(mocks); + injector.require(['Magento_Checkout/js/sidebar'], function (Constr) { + sidebar = new Constr; + done(); + }); + }); + + describe('Check remove mini-cart item callback.', function () { + beforeEach(function () { + spyOn(jQuery.fn, 'trigger'); + spyOn(mocks['Magento_Customer/js/customer-data'], 'get').and.returnValue(cart); + }); + + it('Method "_removeItemAfter" is defined', function () { + expect(sidebar._removeItemAfter).toBeDefined(); + }); + + it('Cart item is exists', function () { + var elem = $('<input>').data('cart-item', 5); + + sidebar._removeItemAfter(elem); + expect(mocks['Magento_Customer/js/customer-data'].get).toHaveBeenCalledWith('cart'); + expect(jQuery('body').trigger).toHaveBeenCalledWith('ajax:removeFromCart', 'simple'); + }); + + it('Cart item doesn\'t exists', function () { + var elem = $('<input>').data('cart-item', 100); + + sidebar._removeItemAfter(elem); + expect(jQuery('body').trigger).not.toHaveBeenCalled(); + }); + }); + + describe('Check update item quantity callback.', function () { + beforeEach(function () { + spyOn(jQuery.fn, 'trigger'); + spyOn(mocks['Magento_Customer/js/customer-data'], 'get').and.returnValue(cart); + }); + + it('Method "_updateItemQtyAfter" is defined', function () { + expect(sidebar._updateItemQtyAfter).toBeDefined(); + }); + + it('Cart item is exists', function () { + var elem = $('<input>').data('cart-item', 5); + + spyOn(sidebar, '_hideItemButton'); + + sidebar._updateItemQtyAfter(elem); + expect(mocks['Magento_Customer/js/customer-data'].get).toHaveBeenCalledWith('cart'); + expect(jQuery('body').trigger).toHaveBeenCalledWith('ajax:updateCartItemQty', 'simple'); + expect(sidebar._hideItemButton).toHaveBeenCalledWith(elem); + }); + + it('Cart item doesn\'t exists', function () { + var elem = $('<input>').data('cart-item', 100); + + spyOn(sidebar, '_hideItemButton'); + + sidebar._updateItemQtyAfter(elem); + expect(jQuery('body').trigger).not.toHaveBeenCalled(); + expect(sidebar._hideItemButton).toHaveBeenCalledWith(elem); + }); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js index c5b153f7cc100..e1d853e0b1b55 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js @@ -5,19 +5,50 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'Magento_Ui/js/form/element/abstract' -], function (Abstract) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/abstract', function () { - var params, model; - - beforeEach(function () { + var injector = new Squire(), + providerMock = { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }, + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return providerMock; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + dataScope = 'abstract', params = { - dataScope: 'abstract' - }; - model = new Abstract(params); - model.source = jasmine.createSpyObj('model.source', ['set']); + provider: 'provName', + name: '', + index: 'testIndex', + dataScope: dataScope, + service: { + template: 'ui/form/element/helper/service' + } + }, + model; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/abstract', + 'knockoutjs/knockout-es5' + ], function (Constr) { + model = new Constr(params); + + done(); + }); }); describe('initialize method', function () { @@ -50,8 +81,10 @@ define([ var expectedValue = 1; spyOn(model, 'getInitialValue').and.returnValue(expectedValue); + model.service = true; expect(model.setInitialValue()).toEqual(model); expect(model.getInitialValue).toHaveBeenCalled(); + expect(model.source.set).toHaveBeenCalledWith('data.use_default.' + model.index, 0); expect(model.value()).toEqual(expectedValue); }); }); @@ -303,5 +336,11 @@ define([ expect(model.validate).toHaveBeenCalled(); }); }); + describe('serviceDisabled property', function () { + it('check property state', function () { + expect(typeof model.serviceDisabled).toEqual('function'); + expect(model.serviceDisabled()).toBeFalsy(); + }); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/boolean.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/boolean.test.js index 4f64d1f53aa21..282badea0889f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/boolean.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/boolean.test.js @@ -6,18 +6,45 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'Magento_Ui/js/form/element/boolean' -], function (BooleanElement) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/boolean', function () { - var params, model; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, + dataScope = 'dataScope'; - beforeEach(function () { - params = { - dataScope: 'abstract' - }; - model = new BooleanElement(params); + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/boolean', + 'knockoutjs/knockout-es5' + ], function (Constr) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); }); describe('getInitialValue method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date-time.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date-time.test.js index bd314bcc85736..01208a083a696 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date-time.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date-time.test.js @@ -6,23 +6,52 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'Magento_Ui/js/form/element/date', - 'mageUtils', - 'moment' -], function (DateElement, utils, moment) { + 'squire' +], function (Squire) { 'use strict'; - describe('Magento_Ui/js/form/element/date', function () { - var params, model; - - beforeEach(function () { - params = { - dataScope: 'abstract', - options: { - showsTime: true - } - }; - model = new DateElement(params); + describe('Magento_Ui/js/form/element/date-time', function () { + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, utils, moment, + dataScope = 'abstract'; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/date', + 'mageUtils', + 'moment', + 'knockoutjs/knockout-es5' + ], function (Constr, mageUtils, m) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope, + options: { + showsTime: true + } + }); + utils = mageUtils; + moment = m; + + done(); + }); }); it('Check prepareDateTimeFormats function', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js index 0b2290ba7a831..f9d379407fcbd 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js @@ -4,19 +4,50 @@ */ define([ - 'Magento_Ui/js/form/element/date', - 'mageUtils' -], function (DateElement, utils) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/date', function () { - var params, model; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, utils, + dataScope = 'abstract'; - beforeEach(function () { - params = { - dataScope: 'abstract' - }; - model = new DateElement(params); + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/date', + 'mageUtils', + 'knockoutjs/knockout-es5' + ], function (Constr, mageUtils) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope, + options: { + showsTime: true + } + }); + utils = mageUtils; + + done(); + }); }); it('Check prepareDateTimeFormats function', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js index 916733fd2b0a7..46916054a29be 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/file-uploader.test.js @@ -7,28 +7,65 @@ define([ 'jquery', - 'Magento_Ui/js/form/element/file-uploader' -], function ($, FileUploader) { + 'squire' +], function ($, Squire) { 'use strict'; describe('Magento_Ui/js/form/element/file-uploader', function () { - var component; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/core/events': { + on: jasmine.createSpy() + }, + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + component, + dataScope = 'dataScope', + originalJQuery = jQuery.fn; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/file-uploader', + 'knockoutjs/knockout-es5' + ], function (Constr) { + component = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); - beforeEach(function () { - component = new FileUploader({ - dataScope: 'abstract' + done(); }); }); + afterEach(function () { + jQuery.fn = originalJQuery; + }); + describe('initUploader method', function () { it('creates instance of file uploader', function () { var elem = document.createElement('input'); - spyOn($.fn, 'fileupload'); + spyOn(jQuery.fn, 'fileupload'); component.initUploader(elem); - expect($.fn.fileupload).toHaveBeenCalled(); + expect(jQuery.fn.fileupload).toHaveBeenCalled(); + }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/post-code.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/post-code.test.js index 9bddfcd35642a..96bca1bbd8c6b 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/post-code.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/post-code.test.js @@ -5,19 +5,45 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'uiRegistry', - 'Magento_Ui/js/form/element/post-code' -], function (registry, PostCodeElement) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/post-code', function () { - var params, model; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, + dataScope = 'post-code'; - beforeEach(function () { - params = { - dataScope: 'post-code' - }; - model = new PostCodeElement(params); + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/post-code', + 'knockoutjs/knockout-es5' + ], function (Constr) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); }); describe('update method', function () { @@ -31,9 +57,9 @@ define([ } }; - spyOn(registry, 'get').and.returnValue(country); + spyOn(mocks['Magento_Ui/js/lib/registry/registry'], 'get').and.returnValue(country); model.update(value); - expect(registry.get).toHaveBeenCalled(); + expect(mocks['Magento_Ui/js/lib/registry/registry'].get).toHaveBeenCalled(); expect(model.error()).toEqual(false); expect(model.required()).toEqual(false); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/region.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/region.test.js index 8e76d8e90729d..a36d3b0b7fefa 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/region.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/region.test.js @@ -5,19 +5,46 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'uiRegistry', - 'Magento_Ui/js/form/element/region' -], function (registry, RegionElement) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/region', function () { - var params, model; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, + dataScope = 'dataScope'; - beforeEach(function () { - params = { - dataScope: 'region' - }; - model = new RegionElement(params); + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/region', + 'knockoutjs/knockout-es5', + 'Magento_Ui/js/lib/knockout/extender/observable_array' + ], function (Constr) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); }); describe('update method', function () { @@ -31,9 +58,9 @@ define([ } }; - spyOn(registry, 'get').and.returnValue(country); + spyOn(mocks['Magento_Ui/js/lib/registry/registry'], 'get').and.returnValue(country); model.update(value); - expect(registry.get).toHaveBeenCalled(); + expect(mocks['Magento_Ui/js/lib/registry/registry'].get).toHaveBeenCalled(); expect(model.error()).toEqual(false); expect(model.required()).toEqual(false); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js index 6eac936974595..fffb045e8ce41 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js @@ -5,18 +5,49 @@ /*eslint max-nested-callbacks: 0*/ define([ - 'Magento_Ui/js/form/element/select' -], function (SelectElement) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/select', function () { - var params, model; - - beforeEach(function () { + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + options: jasmine.createSpy(), + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy(), + 'Magento_Ui/js/core/renderer/layout': jasmine.createSpy() + }, + dataScope = 'select', params = { - dataScope: 'select' - }; - model = new SelectElement(params); + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }, + model; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/select', + 'knockoutjs/knockout-es5', + 'Magento_Ui/js/lib/knockout/extender/observable_array' + ], function (Constr) { + model = new Constr(params); + + done(); + }); }); describe('initialize method', function () { @@ -24,26 +55,20 @@ define([ expect(model).toBeDefined(); }); it('check for chainable', function () { - spyOn(model, 'initInput'); spyOn(model, 'initFilter'); expect(model.initialize(params)).toEqual(model); - expect(model.initInput).not.toHaveBeenCalled(); expect(model.initFilter).not.toHaveBeenCalled(); }); it('check for call initInput', function () { - spyOn(model, 'initInput'); spyOn(model, 'initFilter'); model.customEntry = true; expect(model.initialize(params)).toEqual(model); - expect(model.initInput).toHaveBeenCalled(); expect(model.initFilter).not.toHaveBeenCalled(); }); it('check for call initFilter', function () { - spyOn(model, 'initInput'); spyOn(model, 'initFilter'); model.filterBy = true; expect(model.initialize(params)).toEqual(model); - expect(model.initInput).not.toHaveBeenCalled(); expect(model.initFilter).toHaveBeenCalled(); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/single-checkbox-use-config.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/single-checkbox-use-config.test.js new file mode 100644 index 0000000000000..f63e0adda1e17 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/single-checkbox-use-config.test.js @@ -0,0 +1,78 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*eslint max-nested-callbacks: 0*/ + +define([ + 'squire' +], function (Squire) { + 'use strict'; + + describe('Magento_Ui/js/form/element/single-checkbox-use-config', function () { + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + dataScope = 'dataScope', + params = { + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }, + model; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/single-checkbox-use-config', + 'knockoutjs/knockout-es5' + ], function (Constr) { + model = new Constr(params); + done(); + }); + }); + + describe('initObservable method', function () { + it('check for chainable', function () { + expect(model.initObservable({})).toEqual(model); + }); + it('check for validation', function () { + spyOn(model, 'observe').and.returnValue(model); + expect(model.initObservable()).toEqual(model); + expect(model.validation).toEqual({}); + }); + }); + + describe('toggleElement method', function () { + it('check with isUseDefault false', function () { + spyOn(model, 'isUseDefault').and.returnValue(false); + spyOn(model, 'isUseConfig').and.returnValue(false); + expect(model.toggleElement()).toEqual(undefined); + expect(model.disabled()).toEqual(false); + expect(model.source.set).toHaveBeenCalledWith('data.use_default.' + model.index, 0); + }); + it('check with isUseDefault true', function () { + spyOn(model, 'isUseDefault').and.returnValue(true); + spyOn(model, 'isUseConfig').and.returnValue(false); + expect(model.toggleElement()).toEqual(undefined); + expect(model.disabled()).toEqual(true); + expect(model.source.set).toHaveBeenCalledWith('data.use_default.' + model.index, 1); + }); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/textarea.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/textarea.test.js index 03b00f0c3a842..60beff8d9b628 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/textarea.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/textarea.test.js @@ -4,18 +4,46 @@ */ define([ - 'Magento_Ui/js/form/element/textarea' -], function (TextareaElement) { + 'squire' +], function (Squire) { 'use strict'; describe('Magento_Ui/js/form/element/textarea', function () { - var params, model; + var injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + options: jasmine.createSpy(), + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + model, + dataScope = 'dataScope'; - beforeEach(function () { - params = { - dataScope: 'textarea' - }; - model = new TextareaElement(params); + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/textarea', + 'knockoutjs/knockout-es5' + ], function (Constr) { + model = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope + }); + + done(); + }); }); it('check if component defined', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js index 52a410ffbf6c2..ac6e230e7ed1c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js @@ -7,29 +7,59 @@ define([ 'underscore', 'uiRegistry', - 'Magento_Ui/js/form/element/ui-select', - 'ko', - 'jquery' -], function (_, registry, Constr, ko, $) { + 'squire', + 'ko' +], function (_, registry, Squire, ko) { 'use strict'; describe('Magento_Ui/js/form/element/ui-select', function () { - var obj, originaljQueryAjax; + var obj, jq, originaljQueryAjax, + injector = new Squire(), + mocks = { + 'Magento_Ui/js/lib/registry/registry': { + /** Method stub. */ + get: function () { + return { + get: jasmine.createSpy(), + set: jasmine.createSpy() + }; + }, + create: jasmine.createSpy(), + set: jasmine.createSpy(), + async: jasmine.createSpy() + }, + '/mage/utils/wrapper': jasmine.createSpy() + }, + dataScope = 'abstract'; + + beforeEach(function (done) { + injector.mock(mocks); + injector.require([ + 'Magento_Ui/js/form/element/ui-select', + 'jquery', + 'knockoutjs/knockout-es5' + ], function (Constr, $) { + obj = new Constr({ + provider: 'provName', + name: '', + index: '', + dataScope: dataScope, + options: { + showsTime: true + } + }); - beforeEach(function () { - obj = new Constr({ - name: 'uiSelect', - dataScope: '', - provider: 'provider' + obj.value = ko.observableArray([]); + obj.cacheOptions.plain = []; + originaljQueryAjax = $.ajax; + jq = $; + done(); }); - - obj.value = ko.observableArray([]); - obj.cacheOptions.plain = []; - originaljQueryAjax = $.ajax; }); afterEach(function () { - $.ajax = originaljQueryAjax; + jq.ajax = originaljQueryAjax; + injector.clean(); }); describe('"initialize" method', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js index 0f50cc2b6b9b1..201959a2598fd 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js @@ -38,14 +38,18 @@ define([ /** Stub */ turnOffInlineTranslation = function () { manageInlineTranslation(false); - }; + }, + + storedConfig; beforeEach(function () { + storedConfig = context.config.config; $(document.body).append(elWithStaticText); $(document.body).append(elWithVariable); }); afterEach(function () { + context.config.config = storedConfig; elWithStaticText.remove(); elWithVariable.remove(); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js index 1dfdfca8b2f50..0703374aaa9d6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -39,6 +39,37 @@ define([ expect(rules['range-words'].handler(value, params)).toBe(true); }); }); + describe('"validate-number" method', function () { + it('Check on empty value', function () { + var value = ''; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on integer', function () { + var value = '125'; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on float', function () { + var value = '1000.50'; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on formatted float', function () { + var value = '1,000,000.50'; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on not a number', function () { + var value = 'string'; + + expect(rules['validate-number'].handler(value)).toBe(false); + }); + }); }); describe('validate-color', function () { diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/TablesWhitelistGenerateCommandTest.php b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/TablesWhitelistGenerateCommandTest.php new file mode 100644 index 0000000000000..77f2d741fc6b5 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/TablesWhitelistGenerateCommandTest.php @@ -0,0 +1,202 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Developer\Console\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Magento\TestFramework\TestCase\SetupTestCase; +use Magento\Framework\Console\Cli; +use Magento\TestFramework\Deploy\CliCommand; + +/** + * The purpose of this test is to verify the declaration:generate:whitelist command. + */ +class TablesWhitelistGenerateCommandTest extends SetupTestCase +{ + /** + * @var CommandTester + */ + private $tester; + + /** + * @var \Magento\Developer\Console\Command\TablesWhitelistGenerateCommand + */ + private $command; + + /** + * @var \Magento\Framework\Component\ComponentRegistrar + */ + private $componentRegistrar; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var CliCommand + */ + private $cliCommand; + + /** + * {@inheritdoc} + */ + public function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->command = $this->objectManager->create( + \Magento\Developer\Console\Command\TablesWhitelistGenerateCommand::class + ); + $this->componentRegistrar = $this->objectManager->create( + \Magento\Framework\Component\ComponentRegistrar::class + ); + $this->cliCommand = $this->objectManager->get(CliCommand::class); + $this->tester = new CommandTester($this->command); + } + + /** + * Execute generate command for whitelist on module Magento_TestSetupDeclarationModule1. + * + * @param array $expectedWhitelistContent + * + * @moduleName Magento_TestSetupDeclarationModule1 + * @dataProvider contentsDataProvider + */ + public function testExecute(array $expectedWhitelistContent) + { + $moduleName = 'Magento_TestSetupDeclarationModule1'; + $this->cliCommand->install([$moduleName]); + $modulePath = $this->componentRegistrar->getPath('module', $moduleName); + $whiteListFileName = $modulePath + . DIRECTORY_SEPARATOR + . \Magento\Framework\Module\Dir::MODULE_ETC_DIR + . DIRECTORY_SEPARATOR + . \Magento\Framework\Setup\Declaration\Schema\Diff\Diff::GENERATED_WHITELIST_FILE_NAME; + + //run bin/magento declaration:generate:whitelist Magento_TestSetupDeclarationModule1 command. + $this->tester->execute(['--module-name' => $moduleName], ['interactive' => false]); + + $this->assertSame(Cli::RETURN_SUCCESS, $this->tester->getStatusCode()); + + $this->assertFileExists($whiteListFileName); + $this->assertContains('', $this->tester->getDisplay()); + + $whitelistContent = json_decode(file_get_contents($whiteListFileName), true); + $this->assertEquals($expectedWhitelistContent, $whitelistContent); + } + + /** + * Data provider for whitelist contents. + * + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function contentsDataProvider(): array + { + return [ + [ + 'content' => [ + 'reference_table' => + [ + 'column' => + [ + 'tinyint_ref' => true, + 'tinyint_without_padding' => true, + 'bigint_without_padding' => true, + 'integer_without_padding' => true, + 'smallint_with_big_padding' => true, + 'smallint_without_default' => true, + 'int_without_unsigned' => true, + 'int_unsigned' => true, + 'bigint_default_nullable' => true, + 'bigint_not_default_not_nullable' => true, + 'smallint_without_padding' => true, + ], + 'constraint' => + [ + 'tinyint_primary' => true, + ], + ], + 'auto_increment_test' => + [ + 'column' => + [ + 'int_auto_increment_with_nullable' => true, + 'int_disabled_auto_increment' => true, + ], + 'constraint' => + [ + 'AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE' => true, + ], + ], + 'test_table' => + [ + 'column' => + [ + 'smallint' => true, + 'tinyint' => true, + 'bigint' => true, + 'float' => true, + 'double' => true, + 'decimal' => true, + 'date' => true, + 'timestamp' => true, + 'datetime' => true, + 'longtext' => true, + 'mediumtext' => true, + 'varchar' => true, + 'mediumblob' => true, + 'blob' => true, + 'boolean' => true, + 'varbinary_rename' => true, + ], + 'index' => + [ + 'TEST_TABLE_TINYINT_BIGINT' => true, + ], + 'constraint' => + [ + 'TEST_TABLE_SMALLINT_BIGINT' => true, + 'TEST_TABLE_TINYINT_REFERENCE_TABLE_TINYINT_REF' => true, + ], + ], + 'store' => + [ + 'column' => + [ + 'store_owner_id' => true, + 'store_owner' => true, + ], + 'constraint' => + [ + 'STORE_STORE_OWNER_ID_STORE_OWNER_OWNER_ID' => true, + ], + ], + 'store_owner' => + [ + 'column' => + [ + 'owner_id' => true, + 'label' => true, + ], + 'constraint' => + [ + '' => true, + ], + ], + 'some_table' => + [ + 'column' => + [ + 'some_column' => true, + ], + ], + ], + ], + ]; + } +} diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php new file mode 100644 index 0000000000000..879334d8c553b --- /dev/null +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/MethodArgumentsSniff.php @@ -0,0 +1,587 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sniffs\Annotation; + +use PHP_CodeSniffer\Sniffs\Sniff; +use PHP_CodeSniffer\Files\File; + +/** + * Sniff to validate method arguments annotations + */ +class MethodArgumentsSniff implements Sniff +{ + /** + * @var array + */ + private $validTokensBeforeClosingCommentTag = [ + 'T_WHITESPACE', + 'T_PUBLIC', + 'T_PRIVATE', + 'T_PROTECTED', + 'T_STATIC', + 'T_ABSTRACT', + 'T_FINAL' + ]; + + /** + * @var array + */ + private $invalidTypes = [ + 'null', + 'false', + 'true', + 'self' + ]; + + /** + * @inheritdoc + */ + public function register() : array + { + return [ + T_FUNCTION + ]; + } + + /** + * Validates whether valid token exists before closing comment tag + * + * @param string $type + * @return bool + */ + private function isTokenBeforeClosingCommentTagValid(string $type) : bool + { + return in_array($type, $this->validTokensBeforeClosingCommentTag); + } + + /** + * Validates whether comment block exists + * + * @param File $phpcsFile + * @param int $previousCommentClosePtr + * @param int $stackPtr + * @return bool + */ + private function validateCommentBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : bool + { + $tokens = $phpcsFile->getTokens(); + for ($tempPtr = $previousCommentClosePtr + 1; $tempPtr < $stackPtr; $tempPtr++) { + if (!$this->isTokenBeforeClosingCommentTagValid($tokens[$tempPtr]['type'])) { + return false; + } + } + return true; + } + + /** + * Checks whether the parameter type is invalid + * + * @param string $type + * @return bool + */ + private function isInvalidType(string $type) : bool + { + return in_array(strtolower($type), $this->invalidTypes); + } + + /** + * Get arguments from method signature + * + * @param File $phpcsFile + * @param int $openParenthesisPtr + * @param int $closedParenthesisPtr + * @return array + */ + private function getMethodArguments(File $phpcsFile, int $openParenthesisPtr, int $closedParenthesisPtr) : array + { + $tokens = $phpcsFile->getTokens(); + $methodArguments = []; + for ($i = $openParenthesisPtr; $i < $closedParenthesisPtr; $i++) { + $argumentsPtr = $phpcsFile->findNext(T_VARIABLE, $i + 1, $closedParenthesisPtr); + if ($argumentsPtr === false) { + break; + } elseif ($argumentsPtr < $closedParenthesisPtr) { + $arguments = $tokens[$argumentsPtr]['content']; + $methodArguments[] = $arguments; + $i = $argumentsPtr - 1; + } + } + return $methodArguments; + } + + /** + * Get parameters from method annotation + * + * @param array $paramDefinitions + * @return array + */ + private function getMethodParameters(array $paramDefinitions) : array + { + $paramName = []; + for ($i = 0; $i < count($paramDefinitions); $i++) { + if (isset($paramDefinitions[$i]['paramName'])) { + $paramName[] = $paramDefinitions[$i]['paramName']; + } + } + return $paramName; + } + + /** + * Validates whether @inheritdoc without braces [@inheritdoc] exists or not + * + * @param File $phpcsFile + * @param int $previousCommentOpenPtr + * @param int $previousCommentClosePtr + */ + private function validateInheritdocAnnotationWithoutBracesExists( + File $phpcsFile, + int $previousCommentOpenPtr, + int $previousCommentClosePtr + ) : bool { + return $this->validateInheritdocAnnotationExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr, + '@inheritdoc' + ); + } + + /** + * Validates whether @inheritdoc with braces [{@inheritdoc}] exists or not + * + * @param File $phpcsFile + * @param int $previousCommentOpenPtr + * @param int $previousCommentClosePtr + */ + private function validateInheritdocAnnotationWithBracesExists( + File $phpcsFile, + int $previousCommentOpenPtr, + int $previousCommentClosePtr + ) : bool { + return $this->validateInheritdocAnnotationExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr, + '{@inheritdoc}' + ); + } + + /** + * Validates inheritdoc annotation exists + * + * @param File $phpcsFile + * @param int $previousCommentOpenPtr + * @param int $previousCommentClosePtr + * @param string $inheritdocAnnotation + * @return bool + */ + private function validateInheritdocAnnotationExists( + File $phpcsFile, + int $previousCommentOpenPtr, + int $previousCommentClosePtr, + string $inheritdocAnnotation + ) : bool { + $tokens = $phpcsFile->getTokens(); + for ($ptr = $previousCommentOpenPtr; $ptr < $previousCommentClosePtr; $ptr++) { + if (strtolower($tokens[$ptr]['content']) === $inheritdocAnnotation) { + return true; + } + } + return false; + } + + /** + * Validates if annotation exists for parameter in method annotation + * + * @param File $phpcsFile + * @param int $argumentsCount + * @param int $parametersCount + * @param int $previousCommentOpenPtr + * @param int $previousCommentClosePtr + * @param int $stackPtr + */ + private function validateParameterAnnotationForArgumentExists( + File $phpcsFile, + int $argumentsCount, + int $parametersCount, + int $previousCommentOpenPtr, + int $previousCommentClosePtr, + int $stackPtr + ) : void { + if ($argumentsCount > 0 && $parametersCount === 0) { + $inheritdocAnnotationWithoutBracesExists = $this->validateInheritdocAnnotationWithoutBracesExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr + ); + $inheritdocAnnotationWithBracesExists = $this->validateInheritdocAnnotationWithBracesExists( + $phpcsFile, + $previousCommentOpenPtr, + $previousCommentClosePtr + ); + if ($inheritdocAnnotationWithBracesExists) { + $phpcsFile->addFixableError( + '{@inheritdoc} does not import parameter annotation', + $stackPtr, + 'MethodArguments' + ); + } elseif ($this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr) + && !$inheritdocAnnotationWithoutBracesExists + ) { + $phpcsFile->addFixableError( + 'Missing @param for argument in method annotation', + $stackPtr, + 'MethodArguments' + ); + } + } + } + + /** + * Validates whether comment block have extra the parameters listed in method annotation + * + * @param File $phpcsFile + * @param int $argumentsCount + * @param int $parametersCount + * @param int $stackPtr + */ + private function validateCommentBlockDoesnotHaveExtraParameterAnnotation( + File $phpcsFile, + int $argumentsCount, + int $parametersCount, + int $stackPtr + ) : void { + if ($argumentsCount < $parametersCount && $argumentsCount > 0) { + $phpcsFile->addFixableError( + 'Extra @param found in method annotation', + $stackPtr, + 'MethodArguments' + ); + } elseif ($argumentsCount > 0 && $argumentsCount != $parametersCount && $parametersCount != 0) { + $phpcsFile->addFixableError( + '@param is not found for one or more params in method annotation', + $stackPtr, + 'MethodArguments' + ); + } + } + + /** + * Validates whether the argument name exists in method parameter annotation + * + * @param int $stackPtr + * @param int $ptr + * @param File $phpcsFile + * @param array $methodArguments + * @param array $paramDefinitions + */ + private function validateArgumentNameInParameterAnnotationExists( + int $stackPtr, + int $ptr, + File $phpcsFile, + array $methodArguments, + array $paramDefinitions + ) : void { + $parameterNames = $this->getMethodParameters($paramDefinitions); + if (!in_array($methodArguments[$ptr], $parameterNames)) { + $error = $methodArguments[$ptr]. ' parameter is missing in method annotation'; + $phpcsFile->addFixableError($error, $stackPtr, 'MethodArguments'); + } + } + + /** + * Validates whether parameter present in method signature + * + * @param int $ptr + * @param int $paramDefinitionsArguments + * @param array $methodArguments + * @param File $phpcsFile + * @param array $paramPointers + */ + private function validateParameterPresentInMethodSignature( + int $ptr, + string $paramDefinitionsArguments, + array $methodArguments, + File $phpcsFile, + array $paramPointers + ) : void { + if (!in_array($paramDefinitionsArguments, $methodArguments)) { + $phpcsFile->addFixableError( + $paramDefinitionsArguments . ' parameter is missing in method arguments signature', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + } + + /** + * Validates whether the parameters are in order or not in method annotation + * + * @param array $paramDefinitions + * @param array $methodArguments + * @param File $phpcsFile + * @param array $paramPointers + */ + private function validateParameterOrderIsCorrect( + array $paramDefinitions, + array $methodArguments, + File $phpcsFile, + array $paramPointers + ) : void { + $parameterNames = $this->getMethodParameters($paramDefinitions); + $paramDefinitionsCount = count($paramDefinitions); + for ($ptr = 0; $ptr < $paramDefinitionsCount; $ptr++) { + if (isset($methodArguments[$ptr]) && isset($parameterNames[$ptr]) + && in_array($methodArguments[$ptr], $parameterNames) + ) { + if ($methodArguments[$ptr] != $parameterNames[$ptr]) { + $phpcsFile->addFixableError( + $methodArguments[$ptr].' parameter is not in order', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + } + } + } + + /** + * Validate whether duplicate annotation present in method annotation + * + * @param int $stackPtr + * @param array $paramDefinitions + * @param array $paramPointers + * @param File $phpcsFile + * @param array $methodArguments + */ + private function validateDuplicateAnnotationDoesnotExists( + int $stackPtr, + array $paramDefinitions, + array $paramPointers, + File $phpcsFile, + array $methodArguments + ) : void { + $argumentsCount = count($methodArguments); + $parametersCount = count($paramPointers); + if ($argumentsCount <= $parametersCount && $argumentsCount > 0) { + $duplicateParameters = []; + for ($i = 0; $i < sizeof($paramDefinitions); $i++) { + if (isset($paramDefinitions[$i]['paramName'])) { + $parameterContent = $paramDefinitions[$i]['paramName']; + for ($j = $i + 1; $j < count($paramDefinitions); $j++) { + if (isset($paramDefinitions[$j]['paramName']) + && $parameterContent === $paramDefinitions[$j]['paramName'] + ) { + $duplicateParameters[] = $parameterContent; + } + } + } + } + foreach ($duplicateParameters as $value) { + $phpcsFile->addFixableError( + $value . ' duplicate found in method annotation', + $stackPtr, + 'MethodArguments' + ); + } + } + } + + /** + * Validate parameter annotation format is correct or not + * + * @param int $ptr + * @param File $phpcsFile + * @param array $methodArguments + * @param array $paramDefinitions + * @param array $paramPointers + */ + private function validateParameterAnnotationFormatIsCorrect( + int $ptr, + File $phpcsFile, + array $methodArguments, + array $paramDefinitions, + array $paramPointers + ) : void { + switch (count($paramDefinitions)) { + case 0: + $phpcsFile->addFixableError( + 'Missing both type and parameter', + $paramPointers[$ptr], + 'MethodArguments' + ); + break; + case 1: + if (preg_match('/^\$.*/', $paramDefinitions[0])) { + $phpcsFile->addError( + 'Type is not specified', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + break; + case 2: + if ($this->isInvalidType($paramDefinitions[0])) { + $phpcsFile->addFixableError( + $paramDefinitions[0].' is not a valid PHP type', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + $this->validateParameterPresentInMethodSignature( + $ptr, + ltrim($paramDefinitions[1], '&'), + $methodArguments, + $phpcsFile, + $paramPointers + ); + break; + default: + if (preg_match('/^\$.*/', $paramDefinitions[0])) { + $phpcsFile->addError( + 'Type is not specified', + $paramPointers[$ptr], + 'MethodArguments' + ); + if ($this->isInvalidType($paramDefinitions[0])) { + $phpcsFile->addFixableError( + $paramDefinitions[0].' is not a valid PHP type', + $paramPointers[$ptr], + 'MethodArguments' + ); + } + } + break; + } + } + + /** + * Validate method parameter annotations + * + * @param int $stackPtr + * @param array $paramDefinitions + * @param array $paramPointers + * @param File $phpcsFile + * @param array $methodArguments + * @param int $previousCommentOpenPtr + * @param int $previousCommentClosePtr + */ + private function validateMethodParameterAnnotations( + int $stackPtr, + array $paramDefinitions, + array $paramPointers, + File $phpcsFile, + array $methodArguments, + int $previousCommentOpenPtr, + int $previousCommentClosePtr + ) : void { + $argumentCount = count($methodArguments); + $paramCount = count($paramPointers); + $this->validateParameterAnnotationForArgumentExists( + $phpcsFile, + $argumentCount, + $paramCount, + $previousCommentOpenPtr, + $previousCommentClosePtr, + $stackPtr + ); + $this->validateCommentBlockDoesnotHaveExtraParameterAnnotation( + $phpcsFile, + $argumentCount, + $paramCount, + $stackPtr + ); + $this->validateDuplicateAnnotationDoesnotExists( + $stackPtr, + $paramDefinitions, + $paramPointers, + $phpcsFile, + $methodArguments + ); + $this->validateParameterOrderIsCorrect( + $paramDefinitions, + $methodArguments, + $phpcsFile, + $paramPointers + ); + for ($ptr = 0; $ptr < count($methodArguments); $ptr++) { + $tokens = $phpcsFile->getTokens(); + if (isset($paramPointers[$ptr])) { + $this->validateArgumentNameInParameterAnnotationExists( + $stackPtr, + $ptr, + $phpcsFile, + $methodArguments, + $paramDefinitions + ); + $paramContent = $tokens[$paramPointers[$ptr]+2]['content']; + $paramContentExplode = explode(' ', $paramContent); + $this->validateParameterAnnotationFormatIsCorrect( + $ptr, + $phpcsFile, + $methodArguments, + $paramContentExplode, + $paramPointers + ); + } + } + } + + /** + * @inheritdoc + */ + public function process(File $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + $numTokens = count($tokens); + $previousCommentOpenPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr-1, 0); + $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr-1, 0); + if (!$this->validateCommentBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr)) { + $phpcsFile->addError('Comment block is missing', $stackPtr, 'MethodArguments'); + return; + } + $openParenthesisPtr = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr+1, $numTokens); + $closedParenthesisPtr = $phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr+1, $numTokens); + $methodArguments = $this->getMethodArguments($phpcsFile, $openParenthesisPtr, $closedParenthesisPtr); + $paramPointers = $paramDefinitions = []; + for ($tempPtr = $previousCommentOpenPtr; $tempPtr < $previousCommentClosePtr; $tempPtr++) { + if (strtolower($tokens[$tempPtr]['content']) === '@param') { + $paramPointers[] = $tempPtr; + $paramAnnotationParts = explode(' ', $tokens[$tempPtr+2]['content']); + if (count($paramAnnotationParts) === 1) { + if ((preg_match('/^\$.*/', $paramAnnotationParts[0]))) { + $paramDefinitions[] = [ + 'type' => null, + 'paramName' => rtrim(ltrim($tokens[$tempPtr+2]['content'], '&'), ',') + ]; + } else { + $paramDefinitions[] = [ + 'type' => $tokens[$tempPtr+2]['content'], + 'paramName' => null + ]; + } + } else { + $paramDefinitions[] = [ + 'type' => $paramAnnotationParts[0], + 'paramName' => rtrim(ltrim($paramAnnotationParts[1], '&'), ',') + ]; + } + } + } + $this->validateMethodParameterAnnotations( + $stackPtr, + $paramDefinitions, + $paramPointers, + $phpcsFile, + $methodArguments, + $previousCommentOpenPtr, + $previousCommentClosePtr + ); + } +} diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php index a1fe346aae3b6..3f33d0b2c69d5 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php @@ -46,7 +46,7 @@ public function process(File $phpcsFile, $stackPtr) } if ($tokens[$prevPtr]['line'] === $tokens[$stackPtr]['line']) { - $error = 'Each propery must be on a line by itself'; + $error = 'Each property must be on a line by itself'; $phpcsFile->addError($error, $stackPtr, 'SameLine'); } } diff --git a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php index f41c235a6c0f8..e3cfdf532438c 100644 --- a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php @@ -64,7 +64,7 @@ protected function validateNamespace(File $sourceFile, $stackPtr) 'Cannot use "%s" in namespace as it is reserved since PHP %s', $stackPtr, 'Namespace', - [$namespacePart, $this->reservedWords[$namespacePart]] + [$namespacePart, $this->reservedWords[strtolower($namespacePart)]] ); } $stackPtr++; diff --git a/dev/tests/static/framework/Magento/ruleset.xml b/dev/tests/static/framework/Magento/ruleset.xml index 14d05b466b407..f3e4893ba752f 100644 --- a/dev/tests/static/framework/Magento/ruleset.xml +++ b/dev/tests/static/framework/Magento/ruleset.xml @@ -22,6 +22,10 @@ <rule ref="Magento.LiteralNamespaces.LiteralNamespaces"> <exclude-pattern>*/_files/*</exclude-pattern> </rule> + <rule ref="Magento.Annotation.MethodArguments"> + <!--Disabled due to MAGETWO-94083--> + <exclude-pattern>*</exclude-pattern> + </rule> <rule ref="Magento.Functions.OutputBuffering"> <include-pattern>*/(app/code|vendor|setup/src|lib/internal/Magento)/*</include-pattern> <exclude-pattern>*/lib/internal/Magento/Framework/Image/Adapter/Gd2.php</exclude-pattern> diff --git a/dev/tests/static/phpunit-all.xml.dist b/dev/tests/static/phpunit-all.xml.dist index a0d1f2fe75dc9..3020889e6066b 100644 --- a/dev/tests/static/phpunit-all.xml.dist +++ b/dev/tests/static/phpunit-all.xml.dist @@ -21,6 +21,6 @@ <php> <ini name="date.timezone" value="America/Los_Angeles"/> <!-- TESTCODESTYLE_IS_FULL_SCAN - specify if full scan should be performed for test code style test --> - <const name="TESTCODESTYLE_IS_FULL_SCAN" value="1"/> + <const name="TESTCODESTYLE_IS_FULL_SCAN" value="0"/> </php> </phpunit> diff --git a/dev/tests/static/phpunit.xml.dist b/dev/tests/static/phpunit.xml.dist index d9f9dbdfe54cd..ec581ec992f00 100644 --- a/dev/tests/static/phpunit.xml.dist +++ b/dev/tests/static/phpunit.xml.dist @@ -31,7 +31,7 @@ <php> <ini name="date.timezone" value="America/Los_Angeles"/> <!-- TESTCODESTYLE_IS_FULL_SCAN - specify if full scan should be performed for test code style test --> - <const name="TESTCODESTYLE_IS_FULL_SCAN" value="1"/> + <const name="TESTCODESTYLE_IS_FULL_SCAN" value="0"/> <!-- TESTS_COMPOSER_PATH - specify the path to composer binary, if a relative reference cannot be resolved --> <!--<const name="TESTS_COMPOSER_PATH" value="/usr/local/bin/composer"/>--> </php> diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/ControllerAclTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/ControllerAclTest.php index 187cb9087013e..f204019988b79 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/ControllerAclTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/ControllerAclTest.php @@ -232,7 +232,7 @@ private function isItTest($relativeFilePath) */ private function getControllerPath($relativeFilePath) { - if (preg_match('~(Magento\/.*Controller\/Adminhtml\/.*)\.php~', $relativeFilePath, $matches)) { + if (preg_match('~(Magento\/[^\/]+\/Controller\/Adminhtml\/.*)\.php~', $relativeFilePath, $matches)) { if (count($matches) === 2) { $partPath = $matches[1]; return $partPath; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php index 0f7231e19aeed..62816cd4e4f76 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php @@ -10,4 +10,6 @@ 'dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php', 'lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php', 'dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php', + 'lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php', + 'lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php' ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 78ab26401b0ff..12c10990a3af5 100755 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4237,4 +4237,5 @@ ['Zend_Feed', 'Zend\Feed'], ['Zend_Uri', 'Zend\Uri\Uri'], ['Zend_Mime', 'Magento\Framework\HTTP\Mime'], + ['Magento\Framework\Encryption\Crypt', 'Magento\Framework\Encryption\EncryptionAdapterInterface'], ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index 8b811cc6b3fc0..ff8e7db0f4260 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -2528,6 +2528,7 @@ ['_isAttributeValueEmpty', 'Magento\Catalog\Model\ResourceModel\AbstractResource'], ['var_dump', ''], ['each', ''], + ['strval', ''], ['create_function', ''], ['configure', 'Magento\Framework\MessageQueue\BatchConsumer'], [ diff --git a/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php index fa62179ed34f5..21ca0a495dd19 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php @@ -256,10 +256,16 @@ private function getFullWhitelist() } } + /** + * Test code quality using phpcs + */ public function testCodeStyle() { $isFullScan = defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1'; $reportFile = self::$reportDir . '/phpcs_report.txt'; + if (!file_exists($reportFile)) { + touch($reportFile); + } $codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper()); $result = $codeSniffer->run($isFullScan ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml'])); $report = file_get_contents($reportFile); @@ -270,6 +276,9 @@ public function testCodeStyle() ); } + /** + * Test code quality using phpmd + */ public function testCodeMess() { $reportFile = self::$reportDir . '/phpmd_report.txt'; @@ -298,6 +307,9 @@ public function testCodeMess() } } + /** + * Test code quality using phpcpd + */ public function testCopyPaste() { $reportFile = self::$reportDir . '/phpcpd_report.xml'; diff --git a/dev/tools/grunt/configs/autoprefixer.json b/dev/tools/grunt/configs/autoprefixer.json index 78283d030919f..28cd9c8a1255f 100644 --- a/dev/tools/grunt/configs/autoprefixer.json +++ b/dev/tools/grunt/configs/autoprefixer.json @@ -2,7 +2,7 @@ "options": { "browsers": [ "last 2 versions", - "ie 9" + "ie 11" ] }, "setup": { diff --git a/dev/travis/before_script.sh b/dev/travis/before_script.sh index 7cf55ca8083f1..1dccc310c7a20 100755 --- a/dev/travis/before_script.sh +++ b/dev/travis/before_script.sh @@ -13,8 +13,8 @@ case $TEST_SUITE in test_set_list=$(find testsuite/* -maxdepth 1 -mindepth 1 -type d | sort) test_set_count=$(printf "$test_set_list" | wc -l) - test_set_size[1]=$(printf "%.0f" $(echo "$test_set_count*0.12" | bc)) #12% - test_set_size[2]=$(printf "%.0f" $(echo "$test_set_count*0.32" | bc)) #32% + test_set_size[1]=$(printf "%.0f" $(echo "$test_set_count*0.17" | bc)) #17% + test_set_size[2]=$(printf "%.0f" $(echo "$test_set_count*0.27" | bc)) #27% test_set_size[3]=$((test_set_count-test_set_size[1]-test_set_size[2])) #56% echo "Total = ${test_set_count}; Batch #1 = ${test_set_size[1]}; Batch #2 = ${test_set_size[2]}; Batch #3 = ${test_set_size[3]};"; @@ -129,6 +129,7 @@ case $TEST_SUITE in sed -e "s?basic?travis_acceptance?g" --in-place ./phpunit.xml cp ./.htaccess.sample ./.htaccess cd ./utils + php -f generate/moduleSequence.php php -f mtf troubleshooting:check-all cd ../../.. diff --git a/lib/internal/Magento/Framework/Acl/AclResource/Config/Converter/Dom.php b/lib/internal/Magento/Framework/Acl/AclResource/Config/Converter/Dom.php index 1b4e70be3644f..68762a8a6c046 100644 --- a/lib/internal/Magento/Framework/Acl/AclResource/Config/Converter/Dom.php +++ b/lib/internal/Magento/Framework/Acl/AclResource/Config/Converter/Dom.php @@ -12,6 +12,7 @@ class Dom implements \Magento\Framework\Config\ConverterInterface * * @param \DOMDocument $source * @return array + * @throws \Exception */ public function convert($source) { diff --git a/lib/internal/Magento/Framework/Acl/AclResource/Config/SchemaLocator.php b/lib/internal/Magento/Framework/Acl/AclResource/Config/SchemaLocator.php index 105f27cb330fc..3cc1087228174 100644 --- a/lib/internal/Magento/Framework/Acl/AclResource/Config/SchemaLocator.php +++ b/lib/internal/Magento/Framework/Acl/AclResource/Config/SchemaLocator.php @@ -27,6 +27,8 @@ public function __construct(\Magento\Framework\Config\Dom\UrnResolver $urnResolv /** * {@inheritdoc} + * + * @throws \Magento\Framework\Exception\NotFoundException */ public function getSchema() { @@ -35,6 +37,8 @@ public function getSchema() /** * {@inheritdoc} + * + * @throws \Magento\Framework\Exception\NotFoundException */ public function getPerFileSchema() { diff --git a/lib/internal/Magento/Framework/Acl/Loader/ResourceLoader.php b/lib/internal/Magento/Framework/Acl/Loader/ResourceLoader.php index c5b75d4ccd3b9..8a4ad39d78570 100644 --- a/lib/internal/Magento/Framework/Acl/Loader/ResourceLoader.php +++ b/lib/internal/Magento/Framework/Acl/Loader/ResourceLoader.php @@ -43,6 +43,7 @@ public function __construct(ProviderInterface $resourceProvider, AclResourceFact * * @param Acl $acl * @return void + * @throws \Zend_Acl_Exception */ public function populateAcl(Acl $acl) { @@ -57,6 +58,7 @@ public function populateAcl(Acl $acl) * @param AclResource $parent * @return void * @throws \InvalidArgumentException + * @throws \Zend_Acl_Exception */ protected function _addResourceTree(Acl $acl, array $resources, AclResource $parent = null) { diff --git a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php index 7b2b94cbd0973..97c24167d47e1 100644 --- a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php +++ b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php @@ -76,7 +76,7 @@ public function getCustomAttribute($attributeCode) */ public function getCustomAttributes() { - return isset($this->_data[self::CUSTOM_ATTRIBUTES]) ? $this->_data[self::CUSTOM_ATTRIBUTES] : []; + return $this->_data[self::CUSTOM_ATTRIBUTES] ?? []; } /** @@ -131,7 +131,7 @@ public function setCustomAttribute($attributeCode, $attributeValue) */ protected function getCustomAttributesCodes() { - return isset($this->customAttributesCodes) ? $this->customAttributesCodes : []; + return $this->customAttributesCodes ?? []; } /** diff --git a/lib/internal/Magento/Framework/Api/AbstractSimpleObject.php b/lib/internal/Magento/Framework/Api/AbstractSimpleObject.php index b907246139d42..9cc0ddab3bfcb 100644 --- a/lib/internal/Magento/Framework/Api/AbstractSimpleObject.php +++ b/lib/internal/Magento/Framework/Api/AbstractSimpleObject.php @@ -34,7 +34,7 @@ public function __construct(array $data = []) */ protected function _get($key) { - return isset($this->_data[$key]) ? $this->_data[$key] : null; + return $this->_data[$key] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Api/CombinedFilterGroup.php b/lib/internal/Magento/Framework/Api/CombinedFilterGroup.php index 21d95b72fed1c..190085ddcae00 100644 --- a/lib/internal/Magento/Framework/Api/CombinedFilterGroup.php +++ b/lib/internal/Magento/Framework/Api/CombinedFilterGroup.php @@ -7,8 +7,6 @@ namespace Magento\Framework\Api; -use Magento\Framework\Api\AbstractSimpleObject; -use Magento\Framework\DB\Select; use Magento\Framework\Exception\InputException; use Magento\Framework\Phrase; diff --git a/lib/internal/Magento/Framework/Api/ImageProcessor.php b/lib/internal/Magento/Framework/Api/ImageProcessor.php index beadb51bc7796..83390b3853212 100644 --- a/lib/internal/Magento/Framework/Api/ImageProcessor.php +++ b/lib/internal/Magento/Framework/Api/ImageProcessor.php @@ -172,7 +172,7 @@ public function processImageContent($entityType, $imageContent) */ protected function getMimeTypeExtension($mimeType) { - return isset($this->mimeTypeExtensionMap[$mimeType]) ? $this->mimeTypeExtensionMap[$mimeType] : ''; + return $this->mimeTypeExtensionMap[$mimeType] ?? ''; } /** diff --git a/lib/internal/Magento/Framework/Api/Search/Document.php b/lib/internal/Magento/Framework/Api/Search/Document.php index d60458a6e5585..7454fa7974ece 100644 --- a/lib/internal/Magento/Framework/Api/Search/Document.php +++ b/lib/internal/Magento/Framework/Api/Search/Document.php @@ -33,9 +33,7 @@ public function setId($id) */ public function getCustomAttribute($attributeCode) { - return isset($this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode]) - ? $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] - : null; + return $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php index c9f10c183b50c..024a4d2af3847 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/FilterProcessor.php @@ -123,6 +123,6 @@ private function getCustomFilterForField($field) */ private function getFieldMapping($field) { - return isset($this->fieldMapping[$field]) ? $this->fieldMapping[$field] : $field; + return $this->fieldMapping[$field] ?? $field; } } diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/JoinProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/JoinProcessor.php index b8e52334bee1f..207325042c737 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/JoinProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/JoinProcessor.php @@ -121,6 +121,6 @@ private function getCustomJoin($field) */ private function getFieldMapping($field) { - return isset($this->fieldMapping[$field]) ? $this->fieldMapping[$field] : $field; + return $this->fieldMapping[$field] ?? $field; } } diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php index 85a6d6f570d5e..9dda5cd682708 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php @@ -59,7 +59,7 @@ public function process(SearchCriteriaInterface $searchCriteria, AbstractDb $col */ private function getFieldMapping($field) { - return isset($this->fieldMapping[$field]) ? $this->fieldMapping[$field] : $field; + return $this->fieldMapping[$field] ?? $field; } /** diff --git a/lib/internal/Magento/Framework/Api/Uploader.php b/lib/internal/Magento/Framework/Api/Uploader.php index d3ae11100b9dc..5cea3a34569a9 100644 --- a/lib/internal/Magento/Framework/Api/Uploader.php +++ b/lib/internal/Magento/Framework/Api/Uploader.php @@ -19,7 +19,7 @@ public function __construct() } /** - * Explicitly set the the file attributes instead of setting it via constructor + * Explicitly set the file attributes instead of setting it via constructor * * @param array $fileAttributes * @return void diff --git a/lib/internal/Magento/Framework/App/ActionFlag.php b/lib/internal/Magento/Framework/App/ActionFlag.php index 657c2ede9262d..55201504c968f 100644 --- a/lib/internal/Magento/Framework/App/ActionFlag.php +++ b/lib/internal/Magento/Framework/App/ActionFlag.php @@ -65,9 +65,7 @@ public function get($action, $flag = '') $action = $this->_request->getActionName(); } if ('' === $flag) { - return isset( - $this->_flags[$this->_getControllerKey()] - ) ? $this->_flags[$this->_getControllerKey()] : []; + return $this->_flags[$this->_getControllerKey()] ?? []; } elseif (isset($this->_flags[$this->_getControllerKey()][$action][$flag])) { return $this->_flags[$this->_getControllerKey()][$action][$flag]; } else { diff --git a/lib/internal/Magento/Framework/App/AreaList.php b/lib/internal/Magento/Framework/App/AreaList.php index 7c123f7ff9d60..fb28d09d5fe09 100644 --- a/lib/internal/Magento/Framework/App/AreaList.php +++ b/lib/internal/Magento/Framework/App/AreaList.php @@ -88,7 +88,7 @@ public function getCodeByFrontName($frontName) */ public function getFrontName($areaCode) { - return isset($this->_areas[$areaCode]['frontName']) ? $this->_areas[$areaCode]['frontName'] : null; + return $this->_areas[$areaCode]['frontName'] ?? null; } /** @@ -111,7 +111,7 @@ public function getCodes() */ public function getDefaultRouter($areaCode) { - return isset($this->_areas[$areaCode]['router']) ? $this->_areas[$areaCode]['router'] : null; + return $this->_areas[$areaCode]['router'] ?? null; } /** diff --git a/lib/internal/Magento/Framework/App/Bootstrap.php b/lib/internal/Magento/Framework/App/Bootstrap.php index 4468954d189da..904c41ab9ec33 100644 --- a/lib/internal/Magento/Framework/App/Bootstrap.php +++ b/lib/internal/Magento/Framework/App/Bootstrap.php @@ -12,6 +12,7 @@ use Magento\Framework\Autoload\Populator; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Filesystem\DriverPool; +use Psr\Log\LoggerInterface; /** * A bootstrap of Magento application @@ -258,6 +259,7 @@ public function run(AppInterface $application) \Magento\Framework\Profiler::stop('magento'); } catch (\Exception $e) { \Magento\Framework\Profiler::stop('magento'); + $this->objectManager->get(LoggerInterface::class)->error($e->getMessage()); if (!$application->catchException($this, $e)) { throw $e; } @@ -423,7 +425,7 @@ protected function terminate(\Exception $e) if (!$this->objectManager) { throw new \DomainException(); } - $this->objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); + $this->objectManager->get(LoggerInterface::class)->critical($e); } catch (\Exception $e) { $message .= "Could not write error message to log. Please use developer mode to see the message.\n"; } diff --git a/lib/internal/Magento/Framework/App/Cache/Frontend/Factory.php b/lib/internal/Magento/Framework/App/Cache/Frontend/Factory.php index e8eca51aad976..f84dd304643da 100644 --- a/lib/internal/Magento/Framework/App/Cache/Frontend/Factory.php +++ b/lib/internal/Magento/Framework/App/Cache/Frontend/Factory.php @@ -10,9 +10,7 @@ namespace Magento\Framework\App\Cache\Frontend; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Filesystem; -use Magento\Framework\Filesystem\DriverInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -147,15 +145,17 @@ public function create(array $options) $result = $this->_objectManager->create( \Magento\Framework\Cache\Frontend\Adapter\Zend::class, [ - 'frontend' => \Zend_Cache::factory( - $frontend['type'], - $backend['type'], - $frontend, - $backend['options'], - true, - true, - true - ) + 'frontendFactory' => function () use ($frontend, $backend) { + return \Zend_Cache::factory( + $frontend['type'], + $backend['type'], + $frontend, + $backend['options'], + true, + true, + true + ); + } ] ); $result = $this->_applyDecorators($result); diff --git a/lib/internal/Magento/Framework/App/Config/Initial.php b/lib/internal/Magento/Framework/App/Config/Initial.php index 1933682346ad3..b65c9a2f53489 100644 --- a/lib/internal/Magento/Framework/App/Config/Initial.php +++ b/lib/internal/Magento/Framework/App/Config/Initial.php @@ -72,9 +72,9 @@ public function getData($scope) list($scopeType, $scopeCode) = array_pad(explode('|', $scope), 2, null); if (ScopeConfigInterface::SCOPE_TYPE_DEFAULT == $scopeType) { - return isset($this->_data[$scopeType]) ? $this->_data[$scopeType] : []; + return $this->_data[$scopeType] ?? []; } elseif ($scopeCode) { - return isset($this->_data[$scopeType][$scopeCode]) ? $this->_data[$scopeType][$scopeCode] : []; + return $this->_data[$scopeType][$scopeCode] ?? []; } return []; } diff --git a/lib/internal/Magento/Framework/App/CsrfAwareActionInterface.php b/lib/internal/Magento/Framework/App/CsrfAwareActionInterface.php new file mode 100644 index 0000000000000..8413b945a1141 --- /dev/null +++ b/lib/internal/Magento/Framework/App/CsrfAwareActionInterface.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App; + +use Magento\Framework\App\Request\InvalidRequestException; + +/** + * Action that's aware of CSRF protection. + */ +interface CsrfAwareActionInterface extends ActionInterface +{ + /** + * Create exception in case CSRF validation failed. + * Return null if default exception will suffice. + * + * @param RequestInterface $request + * + * @return InvalidRequestException|null + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException; + + /** + * Perform custom request validation. + * Return null if default validation is needed. + * + * @param RequestInterface $request + * + * @return bool|null + */ + public function validateForCsrf(RequestInterface $request): ?bool; +} diff --git a/lib/internal/Magento/Framework/App/DefaultPath/DefaultPath.php b/lib/internal/Magento/Framework/App/DefaultPath/DefaultPath.php index 61d0aa138f9a4..8a4188aed9605 100644 --- a/lib/internal/Magento/Framework/App/DefaultPath/DefaultPath.php +++ b/lib/internal/Magento/Framework/App/DefaultPath/DefaultPath.php @@ -32,6 +32,6 @@ public function __construct(array $parts) */ public function getPart($code) { - return isset($this->_parts[$code]) ? $this->_parts[$code] : null; + return $this->_parts[$code] ?? null; } } diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig.php b/lib/internal/Magento/Framework/App/DeploymentConfig.php index 0fe7703ef81c0..615c295675adc 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig.php @@ -70,7 +70,7 @@ public function get($key = null, $defaultValue = null) if ($key === null) { return $this->flatData; } - return isset($this->flatData[$key]) ? $this->flatData[$key] : $defaultValue; + return $this->flatData[$key] ?? $defaultValue; } /** @@ -117,7 +117,7 @@ public function resetData() } /** - * Check if data from deploy files is avaiable + * Check if data from deploy files is available * * @return bool * @since 100.1.3 diff --git a/lib/internal/Magento/Framework/App/FrontController.php b/lib/internal/Magento/Framework/App/FrontController.php index 02b390289c9a9..b00cb3ed6090f 100644 --- a/lib/internal/Magento/Framework/App/FrontController.php +++ b/lib/internal/Magento/Framework/App/FrontController.php @@ -7,6 +7,17 @@ */ namespace Magento\Framework\App; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\App\Request\ValidatorInterface as RequestValidator; +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Message\ManagerInterface as MessageManager; +use Magento\Framework\App\Action\AbstractAction; +use Psr\Log\LoggerInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class FrontController implements FrontControllerInterface { /** @@ -15,27 +26,54 @@ class FrontController implements FrontControllerInterface protected $_routerList; /** - * @var \Magento\Framework\App\ResponseInterface + * @var ResponseInterface */ protected $response; + /** + * @var RequestValidator + */ + private $requestValidator; + + /** + * @var MessageManager + */ + private $messages; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param RouterListInterface $routerList - * @param \Magento\Framework\App\ResponseInterface $response + * @param ResponseInterface $response + * @param RequestValidator|null $requestValidator + * @param MessageManager|null $messageManager + * @param LoggerInterface|null $logger */ public function __construct( RouterListInterface $routerList, - \Magento\Framework\App\ResponseInterface $response + ResponseInterface $response, + ?RequestValidator $requestValidator = null, + ?MessageManager $messageManager = null, + ?LoggerInterface $logger = null ) { $this->_routerList = $routerList; $this->response = $response; + $this->requestValidator = $requestValidator + ?? ObjectManager::getInstance()->get(RequestValidator::class); + $this->messages = $messageManager + ?? ObjectManager::getInstance()->get(MessageManager::class); + $this->logger = $logger + ?? ObjectManager::getInstance()->get(LoggerInterface::class); } /** * Perform action and generate response * * @param RequestInterface $request - * @return ResponseInterface|\Magento\Framework\Controller\ResultInterface + * @return ResponseInterface|ResultInterface * @throws \LogicException */ public function dispatch(RequestInterface $request) @@ -49,13 +87,10 @@ public function dispatch(RequestInterface $request) try { $actionInstance = $router->match($request); if ($actionInstance) { - $request->setDispatched(true); - $this->response->setNoCacheHeaders(); - if ($actionInstance instanceof \Magento\Framework\App\Action\AbstractAction) { - $result = $actionInstance->dispatch($request); - } else { - $result = $actionInstance->execute(); - } + $result = $this->processRequest( + $request, + $actionInstance + ); break; } } catch (\Magento\Framework\Exception\NotFoundException $e) { @@ -72,4 +107,46 @@ public function dispatch(RequestInterface $request) } return $result; } + + /** + * @param RequestInterface $request + * @param ActionInterface $actionInstance + * @throws NotFoundException + * + * @return ResponseInterface|ResultInterface + */ + private function processRequest( + RequestInterface $request, + ActionInterface $actionInstance + ) { + $request->setDispatched(true); + $this->response->setNoCacheHeaders(); + //Validating request. + try { + $this->requestValidator->validate( + $request, + $actionInstance + ); + + if ($actionInstance instanceof AbstractAction) { + $result = $actionInstance->dispatch($request); + } else { + $result = $actionInstance->execute(); + } + } catch (InvalidRequestException $exception) { + //Validation failed - processing validation results. + $this->logger->debug( + 'Request validation failed for action "' + .get_class($actionInstance) .'"' + ); + $result = $exception->getReplaceResult(); + if ($messages = $exception->getMessages()) { + foreach ($messages as $message) { + $this->messages->addErrorMessage($message); + } + } + } + + return $result; + } } diff --git a/lib/internal/Magento/Framework/App/Http/Context.php b/lib/internal/Magento/Framework/App/Http/Context.php index a5eba2753b510..79a15110234cd 100644 --- a/lib/internal/Magento/Framework/App/Http/Context.php +++ b/lib/internal/Magento/Framework/App/Http/Context.php @@ -84,9 +84,7 @@ public function unsValue($name) */ public function getValue($name) { - return isset($this->data[$name]) - ? $this->data[$name] - : (isset($this->default[$name]) ? $this->default[$name] : null); + return $this->data[$name] ?? ($this->default[$name] ?? null); } /** diff --git a/lib/internal/Magento/Framework/App/HttpRequestInterface.php b/lib/internal/Magento/Framework/App/HttpRequestInterface.php new file mode 100644 index 0000000000000..674ccdb07f49f --- /dev/null +++ b/lib/internal/Magento/Framework/App/HttpRequestInterface.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\App; + +interface HttpRequestInterface +{ + /** + * Returned true if POST request + * + * @return boolean + */ + public function isPost(); + + /** + * Returned true if GET request + * + * @return boolean + */ + public function isGet(); + + /** + * Returned true if PATCH request + * + * @return boolean + */ + public function isPatch(); + + /** + * Returned true if DELETE request + * + * @return boolean + */ + public function isDelete(); + + /** + * Returned true if PUT request + * + * @return boolean + */ + public function isPut(); + + /** + * Returned true if Ajax request + * + * @return boolean + */ + public function isAjax(); +} diff --git a/lib/internal/Magento/Framework/App/Request/CsrfValidator.php b/lib/internal/Magento/Framework/App/Request/CsrfValidator.php new file mode 100644 index 0000000000000..c930fc920907c --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/CsrfValidator.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\Area; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\State as AppState; +use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +/** + * Validate request for being CSRF protected. + */ +class CsrfValidator implements ValidatorInterface +{ + /** + * @var FormKeyValidator + */ + private $formKeyValidator; + + /** + * @var RedirectFactory + */ + private $redirectFactory; + + /** + * @var AppState + */ + private $appState; + + /** + * @param FormKeyValidator $formKeyValidator + * @param RedirectFactory $redirectFactory + * @param AppState $appState + */ + public function __construct( + FormKeyValidator $formKeyValidator, + RedirectFactory $redirectFactory, + AppState $appState + ) { + $this->formKeyValidator = $formKeyValidator; + $this->redirectFactory = $redirectFactory; + $this->appState = $appState; + } + + /** + * @param HttpRequest $request + * @param ActionInterface $action + * + * @return bool + */ + private function validateRequest( + HttpRequest $request, + ActionInterface $action + ): bool { + $valid = null; + if ($action instanceof CsrfAwareActionInterface) { + $valid = $action->validateForCsrf($request); + } + if ($valid === null) { + $valid = !$request->isPost() + || $request->isAjax() + || $this->formKeyValidator->validate($request); + } + + return $valid; + } + + /** + * @param HttpRequest $request + * @param ActionInterface $action + * + * @return InvalidRequestException + */ + private function createException( + HttpRequest $request, + ActionInterface $action + ): InvalidRequestException { + $exception = null; + if ($action instanceof CsrfAwareActionInterface) { + $exception = $action->createCsrfValidationException($request); + } + if (!$exception) { + $response = $this->redirectFactory->create() + ->setRefererOrBaseUrl() + ->setHttpResponseCode(302); + $messages = [ + new Phrase('Invalid Form Key. Please refresh the page.'), + ]; + $exception = new InvalidRequestException($response, $messages); + } + + return $exception; + } + + /** + * @inheritDoc + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void { + try { + $areaCode = $this->appState->getAreaCode(); + } catch (LocalizedException $exception) { + $areaCode = null; + } + if ($request instanceof HttpRequest + && in_array( + $areaCode, + [Area::AREA_FRONTEND, Area::AREA_ADMINHTML], + true + ) + ) { + $valid = $this->validateRequest($request, $action); + if (!$valid) { + throw $this->createException($request, $action); + } + } + } +} diff --git a/lib/internal/Magento/Framework/App/Request/Http.php b/lib/internal/Magento/Framework/App/Request/Http.php index 4421903f40c2e..03a7200fbe203 100644 --- a/lib/internal/Magento/Framework/App/Request/Http.php +++ b/lib/internal/Magento/Framework/App/Request/Http.php @@ -5,6 +5,7 @@ */ namespace Magento\Framework\App\Request; +use Magento\Framework\App\HttpRequestInterface; use Magento\Framework\App\RequestContentInterface; use Magento\Framework\App\RequestSafetyInterface; use Magento\Framework\App\Route\ConfigInterface\Proxy as ConfigInterface; @@ -16,7 +17,7 @@ /** * Http request */ -class Http extends Request implements RequestContentInterface, RequestSafetyInterface +class Http extends Request implements RequestContentInterface, RequestSafetyInterface, HttpRequestInterface { /**#@+ * HTTP Ports @@ -99,7 +100,7 @@ class Http extends Request implements RequestContentInterface, RequestSafetyInte * @param StringUtils $converter * @param ConfigInterface $routeConfig * @param PathInfoProcessorInterface $pathInfoProcessor - * @param ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param \Zend\Uri\UriInterface|string|null $uri * @param array $directFrontNames */ diff --git a/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php b/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php new file mode 100644 index 0000000000000..3d408b0050686 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/InvalidRequestException.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\RuntimeException; +use Magento\Framework\Phrase; + +/** + * Received request is invalid. + */ +class InvalidRequestException extends RuntimeException +{ + /** + * @var ResponseInterface|ResultInterface + */ + private $replaceResult; + + /** + * @var Phrase[]|null + */ + private $messages; + + /** + * @param ResponseInterface|ResultInterface $replaceResult Use this result + * instead of calling action instance. + * @param Phrase[]|null $messages Messages to show to client + * as error messages. + */ + public function __construct($replaceResult, ?array $messages = null) + { + parent::__construct(new Phrase('Invalid request received')); + + $this->replaceResult = $replaceResult; + $this->messages = $messages; + } + + /** + * @return ResponseInterface|ResultInterface + */ + public function getReplaceResult() + { + return $this->replaceResult; + } + + /** + * @return Phrase[]|null + */ + public function getMessages(): ?array + { + return $this->messages; + } +} diff --git a/lib/internal/Magento/Framework/App/Request/ValidatorInterface.php b/lib/internal/Magento/Framework/App/Request/ValidatorInterface.php new file mode 100644 index 0000000000000..f4b1503ac5cb6 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Request/ValidatorInterface.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\App\Request; + +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\RequestInterface; + +/** + * Validate interface before giving passing it to an ActionInterface. + */ +interface ValidatorInterface +{ + /** + * Validate request and throw the exception if it's invalid. + * + * @param RequestInterface $request + * @param ActionInterface $action + * @throws InvalidRequestException If request was invalid. + * + * @return void + */ + public function validate( + RequestInterface $request, + ActionInterface $action + ): void; +} diff --git a/lib/internal/Magento/Framework/App/ResourceConnection.php b/lib/internal/Magento/Framework/App/ResourceConnection.php index 5b9ae925bff27..148053fa44e08 100644 --- a/lib/internal/Magento/Framework/App/ResourceConnection.php +++ b/lib/internal/Magento/Framework/App/ResourceConnection.php @@ -104,9 +104,21 @@ public function getConnection($resourceName = self::DEFAULT_CONNECTION) */ public function closeConnection($resourceName = self::DEFAULT_CONNECTION) { - $processConnectionName = $this->getProcessConnectionName($this->config->getConnectionName($resourceName)); - if (isset($this->connections[$processConnectionName])) { - $this->connections[$processConnectionName] = null; + if ($resourceName === null) { + foreach ($this->connections as $processConnection) { + if ($processConnection !== null) { + $processConnection->closeConnection(); + } + } + $this->connections = []; + } else { + $processConnectionName = $this->getProcessConnectionName($this->config->getConnectionName($resourceName)); + if (isset($this->connections[$processConnectionName])) { + if ($this->connections[$processConnectionName] !== null) { + $this->connections[$processConnectionName]->closeConnection(); + } + $this->connections[$processConnectionName] = null; + } } } diff --git a/lib/internal/Magento/Framework/App/Response/Http/FileFactory.php b/lib/internal/Magento/Framework/App/Response/Http/FileFactory.php index 979ea1c429590..19a89681a2d5f 100644 --- a/lib/internal/Magento/Framework/App/Response/Http/FileFactory.php +++ b/lib/internal/Magento/Framework/App/Response/Http/FileFactory.php @@ -59,6 +59,7 @@ public function create( $dir = $this->_filesystem->getDirectoryWrite($baseDir); $isFile = false; $file = null; + $fileContent = $this->getFileContent($content); if (is_array($content)) { if (!isset($content['type']) || !isset($content['value'])) { throw new \InvalidArgumentException("Invalid arguments. Keys 'type' and 'value' are required."); @@ -76,7 +77,7 @@ public function create( ->setHeader('Pragma', 'public', true) ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) ->setHeader('Content-type', $contentType, true) - ->setHeader('Content-Length', $contentLength === null ? strlen($content) : $contentLength, true) + ->setHeader('Content-Length', $contentLength === null ? strlen($fileContent) : $contentLength, true) ->setHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"', true) ->setHeader('Last-Modified', date('r'), true); @@ -88,7 +89,8 @@ public function create( echo $stream->read(1024); } } else { - $dir->writeFile($fileName, $content); + $dir->writeFile($fileName, $fileContent); + $file = $fileName; $stream = $dir->openFile($fileName, 'r'); while (!$stream->eof()) { echo $stream->read(1024); @@ -102,4 +104,19 @@ public function create( } return $this->_response; } + + /** + * Returns file content for writing. + * + * @param string|array $content + * @return string|array + */ + private function getFileContent($content) + { + if (isset($content['type']) && $content['type'] === 'string') { + return $content['value']; + } + + return $content; + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php b/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php index bfa96a1702879..32e495ed00a82 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php @@ -135,7 +135,7 @@ public function testCreateFilesystemDriverPool() ); /** @var \Magento\Framework\Filesystem\DriverPool $result */ $this->assertInstanceOf(\Magento\Framework\Filesystem\DriverPool::class, $result); - $this->assertInstanceof($driverClass, $result->getDriver('custom')); + $this->assertInstanceOf($driverClass, $result->getDriver('custom')); } public function testGetParams() diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Cache/Frontend/FactoryTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Cache/Frontend/FactoryTest.php index e87eca57c058d..48a8420cfda60 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Cache/Frontend/FactoryTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Cache/Frontend/FactoryTest.php @@ -128,7 +128,7 @@ protected function _buildModelForCreate($enforcedOptions = [], $decorators = []) $processFrontendFunc = function ($class, $params) { switch ($class) { case \Magento\Framework\Cache\Frontend\Adapter\Zend::class: - return new $class($params['frontend']); + return new $class($params['frontendFactory']); case \Magento\Framework\App\Test\Unit\Cache\Frontend\FactoryTest\CacheDecoratorDummy::class: $frontend = $params['frontend']; unset($params['frontend']); diff --git a/lib/internal/Magento/Framework/Archive/Tar.php b/lib/internal/Magento/Framework/Archive/Tar.php index e2a070503f61f..7fe1255e5b859 100644 --- a/lib/internal/Magento/Framework/Archive/Tar.php +++ b/lib/internal/Magento/Framework/Archive/Tar.php @@ -12,7 +12,6 @@ namespace Magento\Framework\Archive; use Magento\Framework\Archive\Helper\File; -use Magento\Framework\Filesystem\DriverInterface; class Tar extends \Magento\Framework\Archive\AbstractArchive implements \Magento\Framework\Archive\ArchiveInterface { diff --git a/lib/internal/Magento/Framework/Cache/Frontend/Adapter/Zend.php b/lib/internal/Magento/Framework/Cache/Frontend/Adapter/Zend.php index c8917a099689b..43d261c1ed078 100644 --- a/lib/internal/Magento/Framework/Cache/Frontend/Adapter/Zend.php +++ b/lib/internal/Magento/Framework/Cache/Frontend/Adapter/Zend.php @@ -16,11 +16,27 @@ class Zend implements \Magento\Framework\Cache\FrontendInterface protected $_frontend; /** - * @param \Zend_Cache_Core $frontend + * Factory that creates the \Zend_Cache_Cores + * + * @var \Closure + */ + private $frontendFactory; + + /** + * The pid that owns the $_frontend object + * + * @var int */ - public function __construct(\Zend_Cache_Core $frontend) + private $pid; + + /** + * @param \Closure $frontendFactory + */ + public function __construct(\Closure $frontendFactory) { - $this->_frontend = $frontend; + $this->frontendFactory = $frontendFactory; + $this->_frontend = $frontendFactory(); + $this->pid = getmypid(); } /** @@ -28,7 +44,7 @@ public function __construct(\Zend_Cache_Core $frontend) */ public function test($identifier) { - return $this->_frontend->test($this->_unifyId($identifier)); + return $this->getFrontEnd()->test($this->_unifyId($identifier)); } /** @@ -36,7 +52,7 @@ public function test($identifier) */ public function load($identifier) { - return $this->_frontend->load($this->_unifyId($identifier)); + return $this->getFrontEnd()->load($this->_unifyId($identifier)); } /** @@ -44,7 +60,7 @@ public function load($identifier) */ public function save($data, $identifier, array $tags = [], $lifeTime = null) { - return $this->_frontend->save($data, $this->_unifyId($identifier), $this->_unifyIds($tags), $lifeTime); + return $this->getFrontEnd()->save($data, $this->_unifyId($identifier), $this->_unifyIds($tags), $lifeTime); } /** @@ -52,13 +68,14 @@ public function save($data, $identifier, array $tags = [], $lifeTime = null) */ public function remove($identifier) { - return $this->_frontend->remove($this->_unifyId($identifier)); + return $this->getFrontEnd()->remove($this->_unifyId($identifier)); } /** * {@inheritdoc} * * @throws \InvalidArgumentException Exception is thrown when non-supported cleaning mode is specified + * @throws \Zend_Cache_Exception */ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, array $tags = []) { @@ -76,7 +93,7 @@ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, array $tags = []) "Magento cache frontend does not support the cleaning mode '{$mode}'." ); } - return $this->_frontend->clean($mode, $this->_unifyIds($tags)); + return $this->getFrontEnd()->clean($mode, $this->_unifyIds($tags)); } /** @@ -84,7 +101,7 @@ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, array $tags = []) */ public function getBackend() { - return $this->_frontend->getBackend(); + return $this->getFrontEnd()->getBackend(); } /** @@ -92,7 +109,7 @@ public function getBackend() */ public function getLowLevelFrontend() { - return $this->_frontend; + return $this->getFrontEnd(); } /** @@ -119,4 +136,20 @@ protected function _unifyIds(array $ids) } return $ids; } + + /** + * Get frontEnd cache adapter for current pid + * + * @return \Zend_Cache_Core + */ + private function getFrontEnd() + { + if (getmypid() === $this->pid) { + return $this->_frontend; + } + $frontendFactory = $this->frontendFactory; + $this->_frontend = $frontendFactory(); + $this->pid = getmypid(); + return $this->_frontend; + } } diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Adapter/ZendTest.php b/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Adapter/ZendTest.php index 4ed199f9c62fc..fb646c7f87622 100644 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Adapter/ZendTest.php +++ b/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Adapter/ZendTest.php @@ -17,7 +17,10 @@ class ZendTest extends \PHPUnit\Framework\TestCase public function testProxyMethod($method, $params, $expectedParams, $expectedResult) { $frontendMock = $this->createMock(\Zend_Cache_Core::class); - $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendMock); + $frontendFactory = function () use ($frontendMock) { + return $frontendMock; + }; + $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendFactory); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ProxyTesting(); $result = $helper->invokeWithExpectations( $object, @@ -82,7 +85,11 @@ public function testCleanException($cleaningMode, $expectedErrorMessage) { $this->expectException('InvalidArgumentException'); $this->expectExceptionMessage($expectedErrorMessage); - $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($this->createMock(\Zend_Cache_Core::class)); + $frontendMock = $this->createMock(\Zend_Cache_Core::class); + $frontendFactory = function () use ($frontendMock) { + return $frontendMock; + }; + $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendFactory); $object->clean($cleaningMode); } @@ -110,7 +117,10 @@ public function cleanExceptionDataProvider() public function testGetLowLevelFrontend() { $frontendMock = $this->createMock(\Zend_Cache_Core::class); - $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendMock); + $frontendFactory = function () use ($frontendMock) { + return $frontendMock; + }; + $object = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendFactory); $this->assertSame($frontendMock, $object->getLowLevelFrontend()); } } diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Decorator/ProfilerTest.php b/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Decorator/ProfilerTest.php index bef4617add1b9..34e50900bc64e 100644 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Decorator/ProfilerTest.php +++ b/lib/internal/Magento/Framework/Cache/Test/Unit/Frontend/Decorator/ProfilerTest.php @@ -63,7 +63,10 @@ public function proxyMethodDataProvider() { $backend = new \Zend_Cache_Backend_BlackHole(); $adaptee = $this->createMock(\Zend_Cache_Core::class); - $lowLevelFrontend = new \Magento\Framework\Cache\Frontend\Adapter\Zend($adaptee); + $frontendFactory = function () use ($adaptee) { + return $adaptee; + }; + $lowLevelFrontend = new \Magento\Framework\Cache\Frontend\Adapter\Zend($frontendFactory); return [ [ diff --git a/lib/internal/Magento/Framework/Code/GeneratedFiles.php b/lib/internal/Magento/Framework/Code/GeneratedFiles.php index f660b3d698293..bc44b361c57ea 100644 --- a/lib/internal/Magento/Framework/Code/GeneratedFiles.php +++ b/lib/internal/Magento/Framework/Code/GeneratedFiles.php @@ -180,7 +180,7 @@ private function disableAllCacheTypes() } /** - * Enables apppropriate cache types in app/etc/env.php based on the passed in $cacheTypes array + * Enables appropriate cache types in app/etc/env.php based on the passed in $cacheTypes array * TODO: to be removed in scope of MAGETWO-53476 * * @param string[] $cacheTypes diff --git a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php index 0e85cf5260e9b..0a54d770300e8 100644 --- a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php +++ b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php @@ -70,7 +70,7 @@ public function getPaths($type) public function getPath($type, $componentName) { self::validateType($type); - return isset(self::$paths[$type][$componentName]) ? self::$paths[$type][$componentName] : null; + return self::$paths[$type][$componentName] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Composer/Test/Unit/ComposerInformationTest.php b/lib/internal/Magento/Framework/Composer/Test/Unit/ComposerInformationTest.php index 27a222e6bceb9..bf67f73009a08 100644 --- a/lib/internal/Magento/Framework/Composer/Test/Unit/ComposerInformationTest.php +++ b/lib/internal/Magento/Framework/Composer/Test/Unit/ComposerInformationTest.php @@ -7,7 +7,6 @@ use Composer\Composer; use Composer\Package\Locker; -use Magento\Framework\Composer\ComposerInformation; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class ComposerInformationTest extends \PHPUnit\Framework\TestCase diff --git a/lib/internal/Magento/Framework/Composer/Test/Unit/DependencyCheckerTest.php b/lib/internal/Magento/Framework/Composer/Test/Unit/DependencyCheckerTest.php index 801d99373bc50..15ceb0c6c2755 100644 --- a/lib/internal/Magento/Framework/Composer/Test/Unit/DependencyCheckerTest.php +++ b/lib/internal/Magento/Framework/Composer/Test/Unit/DependencyCheckerTest.php @@ -69,7 +69,7 @@ function ($input, $buffer) { $buffer->writeln($output); } ); - $composerApp->Expects($this->at(6))->method('run')->willReturnCallback( + $composerApp->expects($this->at(6))->method('run')->willReturnCallback( function ($input, $buffer) { $output = 'magento/package-d requires magento/package-c (1.0)' . PHP_EOL . 'magento/project-community-edition requires magento/package-a (1.0)' . PHP_EOL; diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 52e7137fa3ddf..92f0302d93baf 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Config; /** @@ -127,5 +129,5 @@ class ConfigOptionsListConstants /** * Size of random string generated for store's encryption key */ - const STORE_KEY_RANDOM_STRING_SIZE = 32; + const STORE_KEY_RANDOM_STRING_SIZE = SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES; } diff --git a/lib/internal/Magento/Framework/Config/View.php b/lib/internal/Magento/Framework/Config/View.php index ef9c39e221e86..05863caeec2b6 100644 --- a/lib/internal/Magento/Framework/Config/View.php +++ b/lib/internal/Magento/Framework/Config/View.php @@ -71,7 +71,7 @@ public function __construct( public function getVars($module) { $this->initData(); - return isset($this->data['vars'][$module]) ? $this->data['vars'][$module] : []; + return $this->data['vars'][$module] ?? []; } /** @@ -110,7 +110,7 @@ public function getVarValue($module, $var) public function getMediaEntities($module, $mediaType) { $this->initData(); - return isset($this->data['media'][$module][$mediaType]) ? $this->data['media'][$module][$mediaType] : []; + return $this->data['media'][$module][$mediaType] ?? []; } /** @@ -124,9 +124,7 @@ public function getMediaEntities($module, $mediaType) public function getMediaAttributes($module, $mediaType, $mediaId) { $this->initData(); - return isset($this->data['media'][$module][$mediaType][$mediaId]) - ? $this->data['media'][$module][$mediaType][$mediaId] - : []; + return $this->data['media'][$module][$mediaType][$mediaId] ?? []; } /** @@ -163,7 +161,7 @@ protected function getIdAttributes() public function getExcludedFiles() { $items = $this->getItems(); - return isset($items['file']) ? $items['file'] : []; + return $items['file'] ?? []; } /** @@ -174,7 +172,7 @@ public function getExcludedFiles() public function getExcludedDir() { $items = $this->getItems(); - return isset($items['directory']) ? $items['directory'] : []; + return $items['directory'] ?? []; } /** @@ -185,7 +183,7 @@ public function getExcludedDir() protected function getItems() { $this->initData(); - return isset($this->data['exclude']) ? $this->data['exclude'] : []; + return $this->data['exclude'] ?? []; } /** diff --git a/lib/internal/Magento/Framework/Config/etc/view.xsd b/lib/internal/Magento/Framework/Config/etc/view.xsd index 20b6a7d4fbfd2..b908862b02147 100644 --- a/lib/internal/Magento/Framework/Config/etc/view.xsd +++ b/lib/internal/Magento/Framework/Config/etc/view.xsd @@ -49,13 +49,13 @@ <xs:element name="image" minOccurs="1" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> - <xs:element name="width" type="xs:positiveInteger" minOccurs="0"/> - <xs:element name="height" type="xs:positiveInteger" minOccurs="0"/> - <xs:element name="constrain" type="xs:boolean" minOccurs="0"/> - <xs:element name="aspect_ratio" type="xs:boolean" minOccurs="0"/> - <xs:element name="frame" type="xs:boolean" minOccurs="0"/> - <xs:element name="transparency" type="xs:boolean" minOccurs="0"/> - <xs:element name="background" minOccurs="0"> + <xs:element name="width" type="xs:positiveInteger" minOccurs="0" nillable="true"/> + <xs:element name="height" type="xs:positiveInteger" minOccurs="0" nillable="true"/> + <xs:element name="constrain" type="xs:boolean" minOccurs="0" nillable="true"/> + <xs:element name="aspect_ratio" type="xs:boolean" minOccurs="0" nillable="true"/> + <xs:element name="frame" type="xs:boolean" minOccurs="0" nillable="true"/> + <xs:element name="transparency" type="xs:boolean" minOccurs="0" nillable="true"/> + <xs:element name="background" minOccurs="0" nillable="true"> <xs:simpleType> <xs:restriction base="xs:string"> <xs:pattern value="\[(\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\]"/> diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index c90d8a9acaed6..dabb080cf7ede 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -19,6 +19,7 @@ use Magento\Setup\Console\CompilerPreparation; use Magento\Setup\Model\ObjectManagerProvider; use Symfony\Component\Console; +use Magento\Framework\Config\ConfigOptionsListConstants; /** * Magento 2 CLI Application. @@ -157,6 +158,7 @@ private function initObjectManager() { $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; + $params = $this->documentRootResolver($params); $requestParams = $this->serviceManager->get('magento-init-params'); $appBootstrapKey = Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS; @@ -242,4 +244,27 @@ protected function getVendorCommands($objectManager) return $commands; } + + /** + * Provides updated configuration in + * accordance to document root settings. + * + * @param array $config + * @return array + */ + private function documentRootResolver(array $config = []): array + { + $params = []; + $deploymentConfig = $this->serviceManager->get(DeploymentConfig::class); + if ((bool)$deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_DOCUMENT_ROOT_IS_PUB)) { + $params[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] = [ + DirectoryList::PUB => [DirectoryList::URL_PATH => ''], + DirectoryList::MEDIA => [DirectoryList::URL_PATH => 'media'], + DirectoryList::STATIC_VIEW => [DirectoryList::URL_PATH => 'static'], + DirectoryList::UPLOAD => [DirectoryList::URL_PATH => 'media/upload'], + ]; + } + + return array_merge_recursive($config, $params); + } } diff --git a/lib/internal/Magento/Framework/Console/QuestionPerformer/YesNo.php b/lib/internal/Magento/Framework/Console/QuestionPerformer/YesNo.php index b46612df59296..58606bb38914d 100644 --- a/lib/internal/Magento/Framework/Console/QuestionPerformer/YesNo.php +++ b/lib/internal/Magento/Framework/Console/QuestionPerformer/YesNo.php @@ -66,7 +66,7 @@ public function execute(array $messages, InputInterface $input, OutputInterface } /** - * Creates Question object from from given array of messages. + * Creates Question object from given array of messages. * * @param string[] $messages array of messages * @return Question diff --git a/lib/internal/Magento/Framework/Console/Test/Unit/QuestionPerformer/YesNoTest.php b/lib/internal/Magento/Framework/Console/Test/Unit/QuestionPerformer/YesNoTest.php index d8552f2ba5f31..4a951aa2987a8 100644 --- a/lib/internal/Magento/Framework/Console/Test/Unit/QuestionPerformer/YesNoTest.php +++ b/lib/internal/Magento/Framework/Console/Test/Unit/QuestionPerformer/YesNoTest.php @@ -11,8 +11,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\QuestionFactory; use Symfony\Component\Console\Question\Question; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Phrase; class YesNoTest extends \PHPUnit\Framework\TestCase { diff --git a/lib/internal/Magento/Framework/DB/AbstractMapper.php b/lib/internal/Magento/Framework/DB/AbstractMapper.php index bce53caef8a39..9d043d6de7fbc 100644 --- a/lib/internal/Magento/Framework/DB/AbstractMapper.php +++ b/lib/internal/Magento/Framework/DB/AbstractMapper.php @@ -10,7 +10,6 @@ use Magento\Framework\Data\ObjectFactory; use Magento\Framework\DB\Adapter\AdapterInterface; use Psr\Log\LoggerInterface as Logger; -use Magento\Framework\DataObject; /** * Class AbstractMapper diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index e02c48222ebd4..441da10253a14 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -490,7 +490,7 @@ public function rawFetchRow($sql, $field = null) if (empty($field)) { return $row; } else { - return isset($row[$field]) ? $row[$field] : false; + return $row[$field] ?? false; } } diff --git a/lib/internal/Magento/Framework/DB/Select/GroupRenderer.php b/lib/internal/Magento/Framework/DB/Select/GroupRenderer.php index df046c92203c2..692843aaa37e3 100644 --- a/lib/internal/Magento/Framework/DB/Select/GroupRenderer.php +++ b/lib/internal/Magento/Framework/DB/Select/GroupRenderer.php @@ -6,7 +6,6 @@ namespace Magento\Framework\DB\Select; use Magento\Framework\DB\Select; -use Magento\Framework\DB\Platform; use Magento\Framework\DB\Platform\Quote; /** diff --git a/lib/internal/Magento/Framework/DB/Select/OrderRenderer.php b/lib/internal/Magento/Framework/DB/Select/OrderRenderer.php index 36a075b9af8c9..dfe9c8949c353 100644 --- a/lib/internal/Magento/Framework/DB/Select/OrderRenderer.php +++ b/lib/internal/Magento/Framework/DB/Select/OrderRenderer.php @@ -40,12 +40,12 @@ public function render(Select $select, $sql = '') $order = []; foreach ($select->getPart(Select::ORDER) as $term) { if (is_array($term)) { - if (is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) { + if (is_numeric($term[0]) && (string)(int)$term[0] == $term[0]) { $order[] = (int)trim($term[0]) . ' ' . $term[1]; } else { $order[] = $this->quote->quoteIdentifier($term[0]) . ' ' . $term[1]; } - } elseif (is_numeric($term) && strval(intval($term)) == $term) { + } elseif (is_numeric($term) && (string)(int)$term == $term) { $order[] = (int)trim($term); } else { $order[] = $this->quote->quoteIdentifier($term); diff --git a/lib/internal/Magento/Framework/DB/SelectFactory.php b/lib/internal/Magento/Framework/DB/SelectFactory.php index bdbc35b395af7..3c64e78839c4d 100644 --- a/lib/internal/Magento/Framework/DB/SelectFactory.php +++ b/lib/internal/Magento/Framework/DB/SelectFactory.php @@ -6,7 +6,6 @@ namespace Magento\Framework\DB; -use Magento\Framework\DB\Select; use Magento\Framework\DB\Select\SelectRenderer; use Magento\Framework\DB\Adapter\AdapterInterface; diff --git a/lib/internal/Magento/Framework/DB/TemporaryTableService.php b/lib/internal/Magento/Framework/DB/TemporaryTableService.php index 479cd0c6fddb9..881ebbe5c85ad 100644 --- a/lib/internal/Magento/Framework/DB/TemporaryTableService.php +++ b/lib/internal/Magento/Framework/DB/TemporaryTableService.php @@ -6,11 +6,10 @@ namespace Magento\Framework\DB; use Magento\Framework\DB\Adapter\AdapterInterface; -use Magento\Framework\DB\Select; /** * Class TemporaryTableService creates a temporary table in mysql from a Magento\Framework\DB\Select. - * Use this class to create an index with that that you want to query later for quick data access + * Use this class to create an index with that you want to query later for quick data access * * @api * @since 100.2.0 diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/Adapter/Pdo/MysqlTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/Adapter/Pdo/MysqlTest.php index 0ca9f6846f77d..98fcdc626a971 100644 --- a/lib/internal/Magento/Framework/DB/Test/Unit/Adapter/Pdo/MysqlTest.php +++ b/lib/internal/Magento/Framework/DB/Test/Unit/Adapter/Pdo/MysqlTest.php @@ -315,7 +315,7 @@ public function testAsymmetricRollBackSuccess() } /** - * Test successfull nested transaction + * Test successful nested transaction */ public function testNestedTransactionCommitSuccess() { @@ -337,7 +337,7 @@ public function testNestedTransactionCommitSuccess() } /** - * Test successfull nested transaction + * Test successful nested transaction */ public function testNestedTransactionRollBackSuccess() { @@ -359,7 +359,7 @@ public function testNestedTransactionRollBackSuccess() } /** - * Test successfull nested transaction + * Test successful nested transaction */ public function testNestedTransactionLastRollBack() { diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/SelectTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/SelectTest.php index 7ce0ba4e4eb45..2be211a6c3da0 100644 --- a/lib/internal/Magento/Framework/DB/Test/Unit/SelectTest.php +++ b/lib/internal/Magento/Framework/DB/Test/Unit/SelectTest.php @@ -7,8 +7,6 @@ use \Magento\Framework\DB\Select; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; - /** * Class SelectTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/Tree/NodeTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/Tree/NodeTest.php deleted file mode 100644 index 93abeae644f61..0000000000000 --- a/lib/internal/Magento/Framework/DB/Test/Unit/Tree/NodeTest.php +++ /dev/null @@ -1,116 +0,0 @@ -<?php -/** - * \Magento\Framework\DB\Tree\Node test case - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\DB\Test\Unit\Tree; - -class NodeTest extends \PHPUnit\Framework\TestCase -{ - /** - * @param array $data - * @param $expectedException - * @param $expectedExceptionMessage - * @dataProvider constructorDataProvider - */ - public function testConstructorWithInvalidArgumentsThrowsException( - array $data, - $expectedException, - $expectedExceptionMessage - ) { - $this->expectException($expectedException); - $this->expectExceptionMessage($expectedExceptionMessage); - new \Magento\Framework\DB\Tree\Node($data['node_data'], $data['keys']); - } - - /** - * @param array $data - * @param string $assertMethod - * @dataProvider isParentDataProvider - */ - public function testIsParent(array $data, $assertMethod) - { - $model = new \Magento\Framework\DB\Tree\Node($data['node_data'], $data['keys']); - $this->$assertMethod($model->isParent()); - } - - /** - * @return array - */ - public function isParentDataProvider() - { - return [ - [ - [ - 'node_data' => [ - 'id' => 'id', - 'pid' => 'pid', - 'level' => 'level', - 'right_key' => 10, - 'left_key' => 5, - ], - 'keys' => [ - 'id' => 'id', - 'pid' => 'pid', - 'level' => 'level', - 'right' => 'right_key', - 'left' => 'left_key', - ], - ], - 'assertTrue', - ], - [ - [ - 'node_data' => [ - 'id' => 'id', - 'pid' => 'pid', - 'level' => 'level', - 'right_key' => 5, - 'left_key' => 10, - ], - 'keys' => [ - 'id' => 'id', - 'pid' => 'pid', - 'level' => 'level', - 'right' => 'right_key', - 'left' => 'left_key', - ], - ], - 'assertFalse' - ] - ]; - } - - /** - * @return array - */ - public function constructorDataProvider() - { - return [ - [ - [ - 'node_data' => null, - 'keys' => null, - ], \Magento\Framework\Exception\LocalizedException::class, - 'The node information is empty. Enter the information and try again.', - ], - [ - [ - 'node_data' => null, - 'keys' => true, - ], \Magento\Framework\Exception\LocalizedException::class, - 'The node information is empty. Enter the information and try again.' - ], - [ - [ - 'node_data' => true, - 'keys' => null, - ], \Magento\Framework\Exception\LocalizedException::class, - 'The encryption key can\'t be empty. Enter the key and try again.' - ] - ]; - } -} diff --git a/lib/internal/Magento/Framework/DB/Tree.php b/lib/internal/Magento/Framework/DB/Tree.php index a162640e5aa50..9890c6ef0d240 100644 --- a/lib/internal/Magento/Framework/DB/Tree.php +++ b/lib/internal/Magento/Framework/DB/Tree.php @@ -15,6 +15,8 @@ * Magento Library * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @deprecated Not used anymore. */ class Tree { @@ -77,6 +79,8 @@ class Tree * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * + * @deprecated Not used anymore. */ public function __construct($config = []) { @@ -149,6 +153,8 @@ public function __construct($config = []) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setIdField($name) { @@ -161,6 +167,8 @@ public function setIdField($name) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setLeftField($name) { @@ -173,6 +181,8 @@ public function setLeftField($name) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setRightField($name) { @@ -185,6 +195,8 @@ public function setRightField($name) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setLevelField($name) { @@ -197,6 +209,8 @@ public function setLevelField($name) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setPidField($name) { @@ -209,6 +223,8 @@ public function setPidField($name) * * @param string $name * @return $this + * + * @deprecated Not used anymore. */ public function setTable($name) { @@ -218,6 +234,8 @@ public function setTable($name) /** * @return array + * + * @deprecated Not used anymore. */ public function getKeys() { @@ -235,6 +253,8 @@ public function getKeys() * * @param array $data * @return string + * + * @deprecated Not used anymore. */ public function clear($data = []) { @@ -260,6 +280,8 @@ public function clear($data = []) * * @param string|int $nodeId * @return array + * + * @deprecated Not used anymore. */ public function getNodeInfo($nodeId) { @@ -279,6 +301,8 @@ public function getNodeInfo($nodeId) * @param array $data * @return false|string * @SuppressWarnings(PHPMD.ExitExpression) + * + * @deprecated Not used anymore. */ public function appendChild($nodeId, $data) { @@ -345,6 +369,8 @@ public function appendChild($nodeId, $data) /** * @return array + * + * @deprecated Not used anymore. */ public function checkNodes() { @@ -375,6 +401,8 @@ public function checkNodes() /** * @param string|int $nodeId * @return bool|Node|void + * + * @deprecated Not used anymore. */ public function removeNode($nodeId) { @@ -450,6 +478,8 @@ public function removeNode($nodeId) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.ExitExpression) + * + * @deprecated Not used anymore. */ public function moveNode($eId, $pId, $aId = 0) { @@ -785,6 +815,8 @@ public function moveNode($eId, $pId, $aId = 0) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @SuppressWarnings(PHPMD.ExitExpression) + * + * @deprecated Not used anymore. */ public function moveNodes($eId, $pId, $aId = 0) { @@ -983,6 +1015,8 @@ public function moveNodes($eId, $pId, $aId = 0) * @param string $joinCondition * @param string $fields * @return void + * + * @deprecated Not used anymore. */ public function addTable($tableName, $joinCondition, $fields = '*') { @@ -992,6 +1026,8 @@ public function addTable($tableName, $joinCondition, $fields = '*') /** * @param Select $select * @return void + * + * @deprecated Not used anymore. */ protected function _addExtTablesToSelect(Select &$select) { @@ -1006,6 +1042,8 @@ protected function _addExtTablesToSelect(Select &$select) * @param int $endLevel * @return NodeSet * @SuppressWarnings(PHPMD.ExitExpression) + * + * @deprecated Not used anymore. */ public function getChildren($nodeId, $startLevel = 0, $endLevel = 0) { @@ -1051,6 +1089,8 @@ public function getChildren($nodeId, $startLevel = 0, $endLevel = 0) /** * @param string|int $nodeId * @return Node + * + * @deprecated Not used anymore. */ public function getNode($nodeId) { diff --git a/lib/internal/Magento/Framework/DB/Tree/Node.php b/lib/internal/Magento/Framework/DB/Tree/Node.php index 8088718be5e73..eb954a696e21e 100644 --- a/lib/internal/Magento/Framework/DB/Tree/Node.php +++ b/lib/internal/Magento/Framework/DB/Tree/Node.php @@ -10,6 +10,8 @@ /** * @SuppressWarnings(PHPMD.UnusedPrivateField) + * + * @deprecated Not used anymore. */ class Node { @@ -50,11 +52,15 @@ class Node /** * @var bool + * + * @deprecated */ public $hasChild = false; /** * @var float|int + * + * @deprecated */ public $numChild = 0; @@ -62,6 +68,8 @@ class Node * @param array $nodeData * @param array $keys * @throws LocalizedException + * + * @deprecated */ public function __construct($nodeData, $keys) { @@ -94,6 +102,8 @@ public function __construct($nodeData, $keys) /** * @param string $name * @return null|array + * + * @deprecated */ public function getData($name) { @@ -106,6 +116,8 @@ public function getData($name) /** * @return int + * + * @deprecated */ public function getLevel() { @@ -114,6 +126,8 @@ public function getLevel() /** * @return int + * + * @deprecated */ public function getLeft() { @@ -122,6 +136,8 @@ public function getLeft() /** * @return int + * + * @deprecated */ public function getRight() { @@ -130,6 +146,8 @@ public function getRight() /** * @return string|int + * + * @deprecated */ public function getPid() { @@ -138,6 +156,8 @@ public function getPid() /** * @return string|int + * + * @deprecated */ public function getId() { @@ -148,6 +168,8 @@ public function getId() * Return true if node has child * * @return bool + * + * @deprecated */ public function isParent() { diff --git a/lib/internal/Magento/Framework/DB/Tree/NodeSet.php b/lib/internal/Magento/Framework/DB/Tree/NodeSet.php index 46a2497a8343f..75e677b77ae45 100644 --- a/lib/internal/Magento/Framework/DB/Tree/NodeSet.php +++ b/lib/internal/Magento/Framework/DB/Tree/NodeSet.php @@ -8,6 +8,7 @@ /** * TODO implements iterators * + * @deprecated Not used anymore. */ class NodeSet implements \Iterator, \Countable { @@ -33,6 +34,8 @@ class NodeSet implements \Iterator, \Countable /** * Constructor + * + * @deprecated */ public function __construct() { @@ -45,6 +48,8 @@ public function __construct() /** * @param Node $node * @return int + * + * @deprecated */ public function addNode(Node $node) { @@ -55,6 +60,8 @@ public function addNode(Node $node) /** * @return int + * + * @deprecated */ public function count() { @@ -63,6 +70,8 @@ public function count() /** * @return bool + * + * @deprecated */ public function valid() { @@ -71,6 +80,8 @@ public function valid() /** * @return false|int + * + * @deprecated */ public function next() { @@ -83,6 +94,8 @@ public function next() /** * @return int + * + * @deprecated */ public function key() { @@ -91,6 +104,8 @@ public function key() /** * @return Node + * + * @deprecated */ public function current() { @@ -99,6 +114,8 @@ public function current() /** * @return void + * + * @deprecated */ public function rewind() { diff --git a/lib/internal/Magento/Framework/Data/AbstractCriteria.php b/lib/internal/Magento/Framework/Data/AbstractCriteria.php index c90ed2b03bf3d..d4811b79980a9 100644 --- a/lib/internal/Magento/Framework/Data/AbstractCriteria.php +++ b/lib/internal/Magento/Framework/Data/AbstractCriteria.php @@ -274,7 +274,7 @@ public function getLimit() */ public function getPart($name, $default = null) { - return isset($this->data[$name]) ? $this->data[$name] : $default; + return $this->data[$name] ?? $default; } /** diff --git a/lib/internal/Magento/Framework/Data/AbstractDataObject.php b/lib/internal/Magento/Framework/Data/AbstractDataObject.php index 5916100ffbbfa..da04fecc447cc 100644 --- a/lib/internal/Magento/Framework/Data/AbstractDataObject.php +++ b/lib/internal/Magento/Framework/Data/AbstractDataObject.php @@ -50,6 +50,6 @@ public function toArray() */ protected function get($key) { - return isset($this->data[$key]) ? $this->data[$key] : null; + return $this->data[$key] ?? null; } } diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index f8b82d3122234..71ec8c1aa8379 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -851,7 +851,7 @@ public function count() */ public function getFlag($flag) { - return isset($this->_flags[$flag]) ? $this->_flags[$flag] : null; + return $this->_flags[$flag] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Checkboxes.php b/lib/internal/Magento/Framework/Data/Form/Element/Checkboxes.php index 76bc4fce5f95c..2a68432207b23 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Checkboxes.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Checkboxes.php @@ -121,13 +121,13 @@ public function getChecked($value) return; } if (!is_array($checked)) { - $checked = [strval($checked)]; + $checked = [(string)$checked]; } else { foreach ($checked as $k => $v) { - $checked[$k] = strval($v); + $checked[$k] = (string)$v; } } - if (in_array(strval($value), $checked)) { + if (in_array((string)$value, $checked)) { return 'checked'; } return; @@ -141,13 +141,13 @@ public function getDisabled($value) { if ($disabled = $this->getData('disabled')) { if (!is_array($disabled)) { - $disabled = [strval($disabled)]; + $disabled = [(string)$disabled]; } else { foreach ($disabled as $k => $v) { - $disabled[$k] = strval($v); + $disabled[$k] = (string)$v; } } - if (in_array(strval($value), $disabled)) { + if (in_array((string)$value, $disabled)) { return 'disabled'; } } @@ -178,14 +178,6 @@ public function getOnchange($value) return; } - // public function getName($value) - // { - // if ($name = $this->getData('name')) { - // return str_replace('$value', $value, $name); - // } - // return ; - // } - /** * @param array $option * @return string diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Date.php b/lib/internal/Magento/Framework/Data/Form/Element/Date.php index c4661c92e8c49..ed5457edc7f3f 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Date.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Date.php @@ -12,7 +12,6 @@ namespace Magento\Framework\Data\Form\Element; use Magento\Framework\Escaper; -use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; class Date extends AbstractElement diff --git a/lib/internal/Magento/Framework/Data/Form/FilterFactory.php b/lib/internal/Magento/Framework/Data/Form/FilterFactory.php index 2763e8bd17cce..22dfabb8fb565 100644 --- a/lib/internal/Magento/Framework/Data/Form/FilterFactory.php +++ b/lib/internal/Magento/Framework/Data/Form/FilterFactory.php @@ -7,7 +7,6 @@ use Magento\Framework\Data\Form\Filter\FilterInterface; use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Phrase; class FilterFactory { diff --git a/lib/internal/Magento/Framework/Data/Form/FormKey/Validator.php b/lib/internal/Magento/Framework/Data/Form/FormKey/Validator.php index 0dbc9c879462e..225ff1fd140a9 100644 --- a/lib/internal/Magento/Framework/Data/Form/FormKey/Validator.php +++ b/lib/internal/Magento/Framework/Data/Form/FormKey/Validator.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Encryption\Helper\Security; + /** * @api */ @@ -32,9 +34,7 @@ public function __construct(\Magento\Framework\Data\Form\FormKey $formKey) public function validate(\Magento\Framework\App\RequestInterface $request) { $formKey = $request->getParam('form_key', null); - if (!$formKey || $formKey !== $this->_formKey->getFormKey()) { - return false; - } - return true; + + return $formKey && Security::compareStrings($formKey, $this->_formKey->getFormKey()); } } diff --git a/lib/internal/Magento/Framework/Data/Structure.php b/lib/internal/Magento/Framework/Data/Structure.php index f2c45fa160cc2..b22395f1ba835 100644 --- a/lib/internal/Magento/Framework/Data/Structure.php +++ b/lib/internal/Magento/Framework/Data/Structure.php @@ -172,7 +172,7 @@ public function createElement($elementId, array $data) */ public function getElement($elementId) { - return isset($this->_elements[$elementId]) ? $this->_elements[$elementId] : false; + return $this->_elements[$elementId] ?? false; } /** @@ -466,9 +466,7 @@ public function getChildId($parentId, $alias) */ public function getChildren($parentId) { - return isset( - $this->_elements[$parentId][self::CHILDREN] - ) ? $this->_elements[$parentId][self::CHILDREN] : []; + return $this->_elements[$parentId][self::CHILDREN] ?? []; } /** @@ -479,7 +477,7 @@ public function getChildren($parentId) */ public function getParentId($childId) { - return isset($this->_elements[$childId][self::PARENT]) ? $this->_elements[$childId][self::PARENT] : false; + return $this->_elements[$childId][self::PARENT] ?? false; } /** diff --git a/lib/internal/Magento/Framework/DataObject/Test/Unit/Copy/Config/SchemaLocatorTest.php b/lib/internal/Magento/Framework/DataObject/Test/Unit/Copy/Config/SchemaLocatorTest.php index c7417439bac1f..0d35b3a4264e0 100644 --- a/lib/internal/Magento/Framework/DataObject/Test/Unit/Copy/Config/SchemaLocatorTest.php +++ b/lib/internal/Magento/Framework/DataObject/Test/Unit/Copy/Config/SchemaLocatorTest.php @@ -6,8 +6,6 @@ namespace Magento\Framework\DataObject\Test\Unit\Copy\Config; -use Magento\Framework\App\Filesystem\DirectoryList; - class SchemaLocatorTest extends \PHPUnit\Framework\TestCase { /** diff --git a/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php b/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php new file mode 100644 index 0000000000000..b9bbb089ae0cc --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Adapter/EncryptionAdapterInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Encryption\Adapter; + +interface EncryptionAdapterInterface +{ + /** + * @param $data + * @return string + */ + public function encrypt(string $data): string; + + /** + * @param string $data + * @return string + */ + public function decrypt(string $data): string; +} diff --git a/lib/internal/Magento/Framework/Encryption/Adapter/Mcrypt.php b/lib/internal/Magento/Framework/Encryption/Adapter/Mcrypt.php new file mode 100644 index 0000000000000..3660b28657b58 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Adapter/Mcrypt.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Encryption\Adapter; + +/** + * Mcrypt adapter for decrypting values using legacy ciphers + */ +class Mcrypt implements EncryptionAdapterInterface +{ + /** + * @var string + */ + private $cipher; + + /** + * @var string + */ + private $mode; + + /** + * @var string + */ + private $initVector; + + /** + * Encryption algorithm module handle + * + * @var resource + */ + private $handle; + + /** + * Mcrypt constructor. + * @param string $key + * @param string $cipher + * @param string $mode + * @param string $initVector + * @throws \Exception + */ + public function __construct( + string $key, + string $cipher = MCRYPT_BLOWFISH, + string $mode = MCRYPT_MODE_ECB, + string $initVector = null + ) { + $this->cipher = $cipher; + $this->mode = $mode; + // @codingStandardsIgnoreLine + $this->handle = @mcrypt_module_open($cipher, '', $mode, ''); + try { + // @codingStandardsIgnoreLine + $maxKeySize = @mcrypt_enc_get_key_size($this->handle); + if (strlen($key) > $maxKeySize) { + throw new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase('Key must not exceed %1 bytes.', [$maxKeySize]) + ); + } + // @codingStandardsIgnoreLine + $initVectorSize = @mcrypt_enc_get_iv_size($this->handle); + if (null === $initVector) { + /* Set vector to zero bytes to not use it */ + $initVector = str_repeat("\0", $initVectorSize); + } elseif (!is_string($initVector) || strlen($initVector) != $initVectorSize) { + throw new \Magento\Framework\Exception\LocalizedException( + new \Magento\Framework\Phrase( + 'Init vector must be a string of %1 bytes.', + [$initVectorSize] + ) + ); + } + $this->initVector = $initVector; + } catch (\Exception $e) { + // @codingStandardsIgnoreLine + @mcrypt_module_close($this->handle); + throw new \Magento\Framework\Exception\LocalizedException(new \Magento\Framework\Phrase($e->getMessage())); + } + // @codingStandardsIgnoreLine + @mcrypt_generic_init($this->handle, $key, $initVector); + } + + /** + * Destructor frees allocated resources + */ + public function __destruct() + { + // @codingStandardsIgnoreStart + @mcrypt_generic_deinit($this->handle); + @mcrypt_module_close($this->handle); + // @codingStandardsIgnoreEnd + } + + /** + * Retrieve a name of currently used cryptographic algorithm + * + * @return string + */ + public function getCipher(): string + { + return $this->cipher; + } + + /** + * Mode in which cryptographic algorithm is running + * + * @return string + */ + public function getMode(): string + { + return $this->mode; + } + + /** + * Retrieve an actual value of initial vector that has been used to initialize a cipher + * + * @return string + */ + public function getInitVector(): ?string + { + return $this->initVector; + } + + /** + * Get the current mcrypt handle + * + * @return resource + */ + public function getHandle() + { + return $this->handle; + } + + /** + * Encrypt a string + * + * @param string $data String to encrypt + * @return string + * @throws \Exception + */ + public function encrypt(string $data): string + { + throw new \Exception( + (string)new \Magento\Framework\Phrase('Mcrypt cannot be used for encryption. Use Sodium instead') + ); + } + + /** + * Decrypt a string + * + * @param string $data + * @return string + */ + public function decrypt(string $data): string + { + if (strlen($data) == 0) { + return $data; + } + // @codingStandardsIgnoreLine + $data = @mdecrypt_generic($this->handle, $data); + /* + * Returned string can in fact be longer than the unencrypted string due to the padding of the data + * @link http://www.php.net/manual/en/function.mdecrypt-generic.php + */ + $data = rtrim($data, "\0"); + return $data; + } +} diff --git a/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php b/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php new file mode 100644 index 0000000000000..9f9facf98ff84 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Adapter/SodiumChachaIetf.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Encryption\Adapter; + +/** + * Sodium adapter for encrypting and decrypting strings + */ +class SodiumChachaIetf implements EncryptionAdapterInterface +{ + /** + * @var string + */ + private $key; + + /** + * Sodium constructor. + * @param string $key + */ + public function __construct( + string $key + ) { + $this->key = $key; + } + + /** + * Encrypt a string + * + * @param string $data + * @return string string + */ + public function encrypt(string $data): string + { + $nonce = random_bytes(SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES); + $cipherText = sodium_crypto_aead_chacha20poly1305_ietf_encrypt( + (string)$data, + $nonce, + $nonce, + $this->key + ); + + return $nonce . $cipherText; + } + + /** + * Decrypt a string + * + * @param string $data + * @return string + */ + public function decrypt(string $data): string + { + $nonce = mb_substr($data, 0, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, '8bit'); + $payload = mb_substr($data, SODIUM_CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES, null, '8bit'); + + $plainText = sodium_crypto_aead_chacha20poly1305_ietf_decrypt( + $payload, + $nonce, + $nonce, + $this->key + ); + + return $plainText; + } +} diff --git a/lib/internal/Magento/Framework/Encryption/Crypt.php b/lib/internal/Magento/Framework/Encryption/Crypt.php index 5f368f0d5b790..d52db40b395ab 100644 --- a/lib/internal/Magento/Framework/Encryption/Crypt.php +++ b/lib/internal/Magento/Framework/Encryption/Crypt.php @@ -4,12 +4,15 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Encryption; /** * Class encapsulates cryptographic algorithm * * @api + * @deprecated */ class Crypt { @@ -29,11 +32,11 @@ class Crypt protected $_initVector; /** - * Encryption algorithm module handle + * Mcrypt adapter * - * @var resource + * @var \Magento\Framework\Encryption\Adapter\Mcrypt */ - protected $_handle; + private $mcrypt; /** * Constructor @@ -53,64 +56,30 @@ public function __construct( $mode = MCRYPT_MODE_ECB, $initVector = false ) { - $this->_cipher = $cipher; - $this->_mode = $mode; - // @codingStandardsIgnoreStart - $this->_handle = @mcrypt_module_open($cipher, '', $mode, ''); - // @codingStandardsIgnoreEnd - try { - // @codingStandardsIgnoreStart - $maxKeySize = @mcrypt_enc_get_key_size($this->_handle); - // @codingStandardsIgnoreEnd - if (strlen($key) > $maxKeySize) { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase('Key must not exceed %1 bytes.', [$maxKeySize]) - ); - } + if (true === $initVector) { // @codingStandardsIgnoreStart - $initVectorSize = @mcrypt_enc_get_iv_size($this->_handle); + $handle = @mcrypt_module_open($cipher, '', $mode, ''); + $initVectorSize = @mcrypt_enc_get_iv_size($handle); // @codingStandardsIgnoreEnd - if (true === $initVector) { - /* Generate a random vector from human-readable characters */ - $abc = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - $initVector = ''; - for ($i = 0; $i < $initVectorSize; $i++) { - $initVector .= $abc[random_int(0, strlen($abc) - 1)]; - } - } elseif (false === $initVector) { - /* Set vector to zero bytes to not use it */ - $initVector = str_repeat("\0", $initVectorSize); - } elseif (!is_string($initVector) || strlen($initVector) != $initVectorSize) { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase( - 'Init vector must be a string of %1 bytes.', - [$initVectorSize] - ) - ); + + /* Generate a random vector from human-readable characters */ + $allowedCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $initVector = ''; + for ($i = 0; $i < $initVectorSize; $i++) { + $initVector .= $allowedCharacters[rand(0, strlen($allowedCharacters) - 1)]; } - $this->_initVector = $initVector; - } catch (\Exception $e) { // @codingStandardsIgnoreStart - @mcrypt_module_close($this->_handle); + @mcrypt_generic_deinit($handle); + @mcrypt_module_close($handle); // @codingStandardsIgnoreEnd - throw $e; } - // @codingStandardsIgnoreStart - @mcrypt_generic_init($this->_handle, $key, $initVector); - // @codingStandardsIgnoreEnd - } - /** - * Destructor frees allocated resources - */ - public function __destruct() - { - // @codingStandardsIgnoreStart - @mcrypt_generic_deinit($this->_handle); - // @codingStandardsIgnoreEnd - // @codingStandardsIgnoreStart - @mcrypt_module_close($this->_handle); - // @codingStandardsIgnoreEnd + $this->mcrypt = new \Magento\Framework\Encryption\Adapter\Mcrypt( + $key, + $cipher, + $mode, + $initVector === false ? null : $initVector + ); } /** @@ -120,7 +89,7 @@ public function __destruct() */ public function getCipher() { - return $this->_cipher; + return $this->mcrypt->getCipher(); } /** @@ -130,7 +99,7 @@ public function getCipher() */ public function getMode() { - return $this->_mode; + return $this->mcrypt->getMode(); } /** @@ -140,7 +109,7 @@ public function getMode() */ public function getInitVector() { - return $this->_initVector; + return $this->mcrypt->getInitVector(); } /** @@ -154,9 +123,8 @@ public function encrypt($data) if (strlen($data) == 0) { return $data; } - // @codingStandardsIgnoreStart - return @mcrypt_generic($this->_handle, $data); - // @codingStandardsIgnoreEnd + // @codingStandardsIgnoreLine + return @mcrypt_generic($this->mcrypt->getHandle(), $data); } /** @@ -167,17 +135,6 @@ public function encrypt($data) */ public function decrypt($data) { - if (strlen($data) == 0) { - return $data; - } - // @codingStandardsIgnoreStart - $data = @mdecrypt_generic($this->_handle, $data); - // @codingStandardsIgnoreEnd - /* - * Returned string can in fact be longer than the unencrypted string due to the padding of the data - * @link http://www.php.net/manual/en/function.mdecrypt-generic.php - */ - $data = rtrim($data, "\0"); - return $data; + return $this->mcrypt->decrypt($data); } } diff --git a/lib/internal/Magento/Framework/Encryption/Encryptor.php b/lib/internal/Magento/Framework/Encryption/Encryptor.php index 881c5843b155e..f7d52d5474ad7 100644 --- a/lib/internal/Magento/Framework/Encryption/Encryptor.php +++ b/lib/internal/Magento/Framework/Encryption/Encryptor.php @@ -3,11 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Framework\Encryption; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Encryption\Adapter\EncryptionAdapterInterface; use Magento\Framework\Encryption\Helper\Security; use Magento\Framework\Math\Random; +use Magento\Framework\Encryption\Adapter\SodiumChachaIetf; +use Magento\Framework\Encryption\Adapter\Mcrypt; /** * Class Encryptor provides basic logic for hashing strings and encrypting/decrypting misc data @@ -56,7 +62,9 @@ class Encryptor implements EncryptorInterface const CIPHER_RIJNDAEL_256 = 2; - const CIPHER_LATEST = 2; + const CIPHER_AEAD_CHACHA20POLY1305 = 3; + + const CIPHER_LATEST = 3; /**#@-*/ /** @@ -108,6 +116,7 @@ class Encryptor implements EncryptorInterface private $random; /** + * Encryptor constructor. * @param Random $random * @param DeploymentConfig $deploymentConfig */ @@ -118,7 +127,7 @@ public function __construct( $this->random = $random; // load all possible keys - $this->keys = preg_split('/\s+/s', trim($deploymentConfig->get(self::PARAM_CRYPT_KEY))); + $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get(self::PARAM_CRYPT_KEY))); $this->keyVersion = count($this->keys) - 1; } @@ -133,7 +142,12 @@ public function __construct( */ public function validateCipher($version) { - $types = [self::CIPHER_BLOWFISH, self::CIPHER_RIJNDAEL_128, self::CIPHER_RIJNDAEL_256]; + $types = [ + self::CIPHER_BLOWFISH, + self::CIPHER_RIJNDAEL_128, + self::CIPHER_RIJNDAEL_256, + self::CIPHER_AEAD_CHACHA20POLY1305, + ]; $version = (int)$version; if (!in_array($version, $types, true)) { @@ -172,7 +186,7 @@ public function getHash($password, $salt = false, $version = self::HASH_VERSION_ */ public function hash($data, $version = self::HASH_VERSION_LATEST) { - return hash($this->hashVersionMap[$version], $data); + return hash($this->hashVersionMap[$version], (string)$data); } /** @@ -260,14 +274,11 @@ private function getPasswordVersion() */ public function encrypt($data) { - $crypt = $this->getCrypt(); - if (null === $crypt) { - return $data; - } - return $this->keyVersion . ':' . $this->cipher . ':' . (MCRYPT_MODE_CBC === - $crypt->getMode() ? $crypt->getInitVector() . ':' : '') . base64_encode( - $crypt->encrypt((string)$data) - ); + $crypt = new SodiumChachaIetf($this->keys[$this->keyVersion]); + + return $this->keyVersion . + ':' . self::CIPHER_AEAD_CHACHA20POLY1305 . + ':' . base64_encode($crypt->encrypt($data)); } /** @@ -279,6 +290,7 @@ public function encrypt($data) * * @param string $data * @return string + * @throws \Exception */ public function decrypt($data) { @@ -286,11 +298,11 @@ public function decrypt($data) $parts = explode(':', $data, 4); $partsCount = count($parts); - $initVector = false; + $initVector = null; // specified key, specified crypt, specified iv if (4 === $partsCount) { list($keyVersion, $cryptVersion, $iv, $data) = $parts; - $initVector = $iv ? $iv : false; + $initVector = $iv ? $iv : null; $keyVersion = (int)$keyVersion; $cryptVersion = self::CIPHER_RIJNDAEL_256; // specified key, specified crypt @@ -325,10 +337,9 @@ public function decrypt($data) } /** - * Return crypt model, instantiate if it is empty + * Validate key contains only allowed characters * * @param string|null $key NULL value means usage of the default key specified on constructor - * @return \Magento\Framework\Encryption\Crypt * @throws \Exception */ public function validateKey($key) @@ -336,7 +347,6 @@ public function validateKey($key) if (preg_match('/\s/s', $key)) { throw new \Exception((string)new \Magento\Framework\Phrase('The encryption key format is invalid.')); } - return $this->getCrypt($key); } /** @@ -344,6 +354,7 @@ public function validateKey($key) * * @param string $key * @return $this + * @throws \Exception */ public function setNewKey($key) { @@ -370,11 +381,15 @@ public function exportKeys() * * @param string $key * @param int $cipherVersion - * @param bool $initVector - * @return Crypt|null + * @param string $initVector + * @return EncryptionAdapterInterface|null + * @throws \Exception */ - protected function getCrypt($key = null, $cipherVersion = null, $initVector = true) - { + private function getCrypt( + string $key = null, + int $cipherVersion = null, + string $initVector = null + ): ?EncryptionAdapterInterface { if (null === $key && null === $cipherVersion) { $cipherVersion = self::CIPHER_RIJNDAEL_256; } @@ -392,6 +407,10 @@ protected function getCrypt($key = null, $cipherVersion = null, $initVector = tr } $cipherVersion = $this->validateCipher($cipherVersion); + if ($cipherVersion >= self::CIPHER_AEAD_CHACHA20POLY1305) { + return new SodiumChachaIetf($key); + } + if ($cipherVersion === self::CIPHER_RIJNDAEL_128) { $cipher = MCRYPT_RIJNDAEL_128; $mode = MCRYPT_MODE_ECB; @@ -403,6 +422,6 @@ protected function getCrypt($key = null, $cipherVersion = null, $initVector = tr $mode = MCRYPT_MODE_ECB; } - return new Crypt($key, $cipher, $mode, $initVector); + return new Mcrypt($key, $cipher, $mode, $initVector); } } diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php new file mode 100644 index 0000000000000..2622441aa0896 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/McryptTest.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +/** + * Test case for \Magento\Framework\Encryption\Adapter\Mcrypt + */ +namespace Magento\Framework\Encryption\Test\Unit\Adapter; + +class McryptTest extends \PHPUnit\Framework\TestCase +{ + private $key; + + private static $cipherInfo; + + private const SUPPORTED_CIPHER_MODE_COMBINATIONS = [ + MCRYPT_BLOWFISH => [MCRYPT_MODE_ECB], + MCRYPT_RIJNDAEL_128 => [MCRYPT_MODE_ECB], + MCRYPT_RIJNDAEL_256 => [MCRYPT_MODE_CBC], + ]; + + protected function setUp() + { + $this->key = substr(__CLASS__, -32, 32); + } + + protected function getRandomString(int $length): string + { + $result = ''; + + do { + $result .= sha1(microtime()); + } while (strlen($result) < $length); + + return substr($result, -$length); + } + + private function requireCipherInfo() + { + $filename = __DIR__ . '/../Crypt/_files/_cipher_info.php'; + + if (!self::$cipherInfo) { + self::$cipherInfo = include $filename; + } + } + + private function getKeySize(string $cipherName, string $modeName): int + { + $this->requireCipherInfo(); + return self::$cipherInfo[$cipherName][$modeName]['key_size']; + } + + private function getInitVectorSize(string $cipherName, string $modeName): int + { + $this->requireCipherInfo(); + return self::$cipherInfo[$cipherName][$modeName]['iv_size']; + } + + public function getCipherModeCombinations(): array + { + $result = []; + foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) { + /** @var array $modes */ + foreach ($modes as $mode) { + $result[$cipher . '-' . $mode] = [$cipher, $mode]; + } + } + return $result; + } + + /** + * @dataProvider getCipherModeCombinations + */ + public function testConstructor(string $cipher, string $mode) + { + /* Generate random init vector */ + $initVector = $this->getRandomString($this->getInitVectorSize($cipher, $mode)); + + $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key, $cipher, $mode, $initVector); + + $this->assertEquals($cipher, $crypt->getCipher()); + $this->assertEquals($mode, $crypt->getMode()); + $this->assertEquals($initVector, $crypt->getInitVector()); + } + + public function getConstructorExceptionData(): array + { + $key = substr(__CLASS__, -32, 32); + $result = []; + foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) { + /** @var array $modes */ + foreach ($modes as $mode) { + $tooLongKey = str_repeat('-', $this->getKeySize($cipher, $mode) + 1); + $tooShortInitVector = str_repeat('-', $this->getInitVectorSize($cipher, $mode) - 1); + $tooLongInitVector = str_repeat('-', $this->getInitVectorSize($cipher, $mode) + 1); + $result['tooLongKey-' . $cipher . '-' . $mode . '-false'] = [$tooLongKey, $cipher, $mode, false]; + $keyPrefix = 'key-' . $cipher . '-' . $mode; + $result[$keyPrefix . '-tooShortInitVector'] = [$key, $cipher, $mode, $tooShortInitVector]; + $result[$keyPrefix . '-tooLongInitVector'] = [$key, $cipher, $mode, $tooLongInitVector]; + } + } + return $result; + } + + /** + * @dataProvider getConstructorExceptionData + * @expectedException \Magento\Framework\Exception\LocalizedException + */ + public function testConstructorException(string $key, string $cipher, string $mode, ?string $initVector = null) + { + new \Magento\Framework\Encryption\Adapter\Mcrypt($key, $cipher, $mode, $initVector); + } + + public function testConstructorDefaults() + { + $cryptExpected = new \Magento\Framework\Encryption\Adapter\Mcrypt( + $this->key, + MCRYPT_BLOWFISH, + MCRYPT_MODE_ECB, + null + ); + $cryptActual = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key); + + $this->assertEquals($cryptExpected->getCipher(), $cryptActual->getCipher()); + $this->assertEquals($cryptExpected->getMode(), $cryptActual->getMode()); + $this->assertEquals($cryptExpected->getInitVector(), $cryptActual->getInitVector()); + } + + public function getCryptData(): array + { + $fixturesFilename = __DIR__ . '/../Crypt/_files/_crypt_fixtures.php'; + + $result = include $fixturesFilename; + /* Restore encoded string back to binary */ + foreach ($result as &$cryptParams) { + $cryptParams[5] = base64_decode($cryptParams[5]); + } + unset($cryptParams); + + return $result; + } + + /** + * @dataProvider getCryptData + */ + public function testDecrypt( + string $key, + string $cipher, + string $mode, + ?string $initVector, + string $expectedData, + string $inputData + ) { + $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($key, $cipher, $mode, $initVector); + $actualData = $crypt->decrypt($inputData); + $this->assertEquals($expectedData, $actualData); + } + + /** + * @expectedException \Exception + */ + public function testEncrypt() + { + $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt($this->key); + $crypt->encrypt('Hello World!!'); + } + + /** + * @dataProvider getCipherModeCombinations + */ + public function testInitVectorNone(string $cipher, string $mode) + { + $crypt = new \Magento\Framework\Encryption\Adapter\Mcrypt( + $this->key, + $cipher, + $mode, + null + ); + $actualInitVector = $crypt->getInitVector(); + + $expectedInitVector = str_repeat("\0", $this->getInitVectorSize($cipher, $mode)); + $this->assertEquals($expectedInitVector, $actualInitVector); + } +} diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php new file mode 100644 index 0000000000000..8092ce55b42c8 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Adapter/SodiumChachaIetfTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +/** + * Test case for \Magento\Framework\Encryption\Adapter\SodiumChachaIetf + */ +namespace Magento\Framework\Encryption\Test\Unit\Adapter; + +class SodiumChachaIetfTest extends \PHPUnit\Framework\TestCase +{ + public function getCryptData(): array + { + $fixturesFilename = __DIR__ . '/../Crypt/_files/_sodium_chachaieft_fixtures.php'; + + $result = include $fixturesFilename; + /* Restore encoded string back to binary */ + foreach ($result as &$cryptParams) { + $cryptParams['encrypted'] = base64_decode($cryptParams['encrypted']); + } + unset($cryptParams); + + return $result; + } + + /** + * @dataProvider getCryptData + */ + public function testEncrypt(string $key, string $encrypted, string $decrypted) + { + $crypt = new \Magento\Framework\Encryption\Adapter\SodiumChachaIetf($key); + $result = $crypt->encrypt($decrypted); + + $this->assertNotEquals($encrypted, $result); + } + + /** + * @dataProvider getCryptData + */ + public function testDecrypt(string $key, string $encrypted, string $decrypted) + { + $crypt = new \Magento\Framework\Encryption\Adapter\SodiumChachaIetf($key); + $result = $crypt->decrypt($encrypted); + + $this->assertEquals($decrypted, $result); + } +} diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php new file mode 100644 index 0000000000000..7917bd9ba83e8 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/Crypt/_files/_sodium_chachaieft_fixtures.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 0 => [ + 'key' => '6wRADHwwCBGgdxbcHhovGB0upmg0mbsN', + 'encrypted' => '146BhsQ3grT0VgkYuY3ii3gpClXHkFqlIcNpAD4+bAMBP+ToCHZHiJID', + 'decrypted' => 'Hello World!!!', + ], + 1 => [ + 'key' => 'uPuzBU067DXTM4PqEi14Sv5tbWjVcRZI', + 'encrypted' => '6SQaVrCnY10n8tOxYyvWuVGKddjR12ZbGylM9K+bRHqsqltRwuLs15vV', + 'decrypted' => 'Hello World!!!', + ], + 2 => [ + 'key' => 'zsmVdKkwVgylxMM8ZzQ3GTv7SxvusKnJ', + 'encrypted' => 'eQcREUJDV8EEB9WA1pBd5LbVQrs4Kyv6iWnkhOnjeitySuPQAcpIVoCM', + 'decrypted' => 'Hello World!!!', + ], + 3 => [ + 'key' => 'aggaHLvRCxRRyebpsrGAdLAIfSrufYrN', + 'encrypted' => 'PSOa8KCpTsxnTgq4IKbpneF38FIp0JeAeiXQIf30vS5X+riylx05pz9b', + 'decrypted' => 'Hello World!!!', + ], + 4 => [ + 'key' => '6tEWnKY6AcdjS2XfPe1DjTbkvu2cFFZo', + 'encrypted' => 'UglO9dEgslFpwPwejJmrK89PmBicv+I1pfdaXaEI69IrETD8LpdzOLF7', + 'decrypted' => 'Hello World!!!', + ], +]; diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php index a37dc981518e5..c286cb0a1d803 100644 --- a/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php @@ -105,6 +105,7 @@ public function testConstructor($cipher, $mode) */ public function getConstructorExceptionData() { + $key = substr(__CLASS__, -32, 32); $result = []; foreach (self::SUPPORTED_CIPHER_MODE_COMBINATIONS as $cipher => $modes) { /** @var array $modes */ @@ -114,8 +115,8 @@ public function getConstructorExceptionData() $tooLongInitVector = str_repeat('-', $this->_getInitVectorSize($cipher, $mode) + 1); $result['tooLongKey-' . $cipher . '-' . $mode . '-false'] = [$tooLongKey, $cipher, $mode, false]; $keyPrefix = 'key-' . $cipher . '-' . $mode; - $result[$keyPrefix . '-tooShortInitVector'] = [$this->_key, $cipher, $mode, $tooShortInitVector]; - $result[$keyPrefix . '-tooLongInitVector'] = [$this->_key, $cipher, $mode, $tooLongInitVector]; + $result[$keyPrefix . '-tooShortInitVector'] = [$key, $cipher, $mode, $tooShortInitVector]; + $result[$keyPrefix . '-tooLongInitVector'] = [$key, $cipher, $mode, $tooLongInitVector]; } } return $result; diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php index f4381a42775d7..f3853a123c156 100644 --- a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php @@ -3,14 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Framework\Encryption\Test\Unit; +use Magento\Framework\Encryption\Adapter\Mcrypt; +use Magento\Framework\Encryption\Adapter\SodiumChachaIetf; use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Encryption\Crypt; -use Magento\Framework\App\DeploymentConfig; class EncryptorTest extends \PHPUnit\Framework\TestCase { + const CRYPT_KEY_1 = 'g9mY9KLrcuAVJfsmVUSRkKFLDdUPVkaZ'; + const CRYPT_KEY_2 = '7wEjmrliuqZQ1NQsndSa8C8WHvddeEbN'; + /** * @var \Magento\Framework\Encryption\Encryptor */ @@ -28,7 +35,7 @@ protected function setUp() $deploymentConfigMock->expects($this->any()) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) - ->will($this->returnValue('cryptKey')); + ->will($this->returnValue(self::CRYPT_KEY_1)); $this->_model = new \Magento\Framework\Encryption\Encryptor($this->_randomGenerator, $deploymentConfigMock); } @@ -102,6 +109,7 @@ public function validateHashDataProvider() * @param mixed $key * * @dataProvider encryptWithEmptyKeyDataProvider + * @expectedException \SodiumException */ public function testEncryptWithEmptyKey($key) { @@ -156,20 +164,27 @@ public function testEncrypt() $actual = $this->_model->encrypt($data); // Extract the initialization vector and encrypted data - $parts = explode(':', $actual, 4); - list(, , $iv, $encryptedData) = $parts; + $parts = explode(':', $actual, 3); + list(, , $encryptedData) = $parts; - // Decrypt returned data with RIJNDAEL_256 cipher, cbc mode - $crypt = new Crypt('cryptKey', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv); + $crypt = new SodiumChachaIetf(self::CRYPT_KEY_1); // Verify decrypted matches original data $this->assertEquals($data, $crypt->decrypt(base64_decode((string)$encryptedData))); } public function testDecrypt() + { + $message = 'Mares eat oats and does eat oats, but little lambs eat ivy.'; + $encrypted = $this->_model->encrypt($message); + + $this->assertEquals($message, $this->_model->decrypt($encrypted)); + } + + public function testLegacyDecrypt() { // sample data to encrypt $data = '0:2:z3a4ACpkU35W6pV692U4ueCVQP0m0v0p:' . - '7ZPIIRZzQrgQH+csfF3fyxYNwbzPTwegncnoTxvI3OZyqKGYlOCTSx5i1KRqNemCC8kuCiOAttLpAymXhzjhNQ=='; + 'DhEG8/uKGGq92ZusqrGb6X/9+2Ng0QZ9z2UZwljgJbs5/A3LaSnqcK0oI32yjHY49QJi+Z7q1EKu2yVqB8EMpA=='; $actual = $this->_model->decrypt($data); @@ -178,7 +193,7 @@ public function testDecrypt() list(, , $iv, $encrypted) = $parts; // Decrypt returned data with RIJNDAEL_256 cipher, cbc mode - $crypt = new Crypt('cryptKey', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv); + $crypt = new Crypt(self::CRYPT_KEY_1, MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $iv); // Verify decrypted matches original data $this->assertEquals($encrypted, base64_encode($crypt->encrypt($actual))); } @@ -189,11 +204,11 @@ public function testEncryptDecryptNewKeyAdded() $deploymentConfigMock->expects($this->at(0)) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) - ->will($this->returnValue("cryptKey1")); + ->will($this->returnValue(self::CRYPT_KEY_1)); $deploymentConfigMock->expects($this->at(1)) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) - ->will($this->returnValue("cryptKey1\ncryptKey2")); + ->will($this->returnValue(self::CRYPT_KEY_1 . "\n" . self::CRYPT_KEY_2)); $model1 = new Encryptor($this->_randomGenerator, $deploymentConfigMock); // simulate an encryption key is being added $model2 = new Encryptor($this->_randomGenerator, $deploymentConfigMock); @@ -209,12 +224,15 @@ public function testEncryptDecryptNewKeyAdded() public function testValidateKey() { - $actual = $this->_model->validateKey('some_key'); - $crypt = new Crypt('some_key', MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC, $actual->getInitVector()); - $expectedEncryptedData = base64_encode($crypt->encrypt('data')); - $actualEncryptedData = base64_encode($actual->encrypt('data')); - $this->assertEquals($expectedEncryptedData, $actualEncryptedData); - $this->assertEquals($crypt->decrypt($expectedEncryptedData), $actual->decrypt($actualEncryptedData)); + $this->_model->validateKey(self::CRYPT_KEY_1); + } + + /** + * @expectedException \Exception + */ + public function testValidateKeyInvalid() + { + $this->_model->validateKey('----- '); } /** diff --git a/lib/internal/Magento/Framework/EntityManager/AbstractModelHydrator.php b/lib/internal/Magento/Framework/EntityManager/AbstractModelHydrator.php index ce35e72baea1f..9b31fcce10b9d 100644 --- a/lib/internal/Magento/Framework/EntityManager/AbstractModelHydrator.php +++ b/lib/internal/Magento/Framework/EntityManager/AbstractModelHydrator.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\EntityManager; -use Magento\Framework\Model\AbstractModel; - /** * Class AbstractModelHydrator */ diff --git a/lib/internal/Magento/Framework/EntityManager/Hydrator.php b/lib/internal/Magento/Framework/EntityManager/Hydrator.php index ccf5b0c8557ab..0ea4eb3cedcd9 100644 --- a/lib/internal/Magento/Framework/EntityManager/Hydrator.php +++ b/lib/internal/Magento/Framework/EntityManager/Hydrator.php @@ -5,10 +5,8 @@ */ namespace Magento\Framework\EntityManager; -use Magento\Framework\EntityManager\MapperPool; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Api\DataObjectHelper; -use Magento\Framework\EntityManager\TypeResolver; /** * Class Hydrator diff --git a/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityDelete.php b/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityDelete.php index 5480b1c4feba9..6bc144929fb60 100644 --- a/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityDelete.php +++ b/lib/internal/Magento/Framework/EntityManager/Observer/BeforeEntityDelete.php @@ -9,7 +9,6 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Event\Observer; use Magento\Framework\Model\AbstractModel; -use Magento\Framework\Model\ResourceModel\Db\AbstractDb; /** * Class BeforeEntityDelete diff --git a/lib/internal/Magento/Framework/EntityManager/Operation/Delete.php b/lib/internal/Magento/Framework/EntityManager/Operation/Delete.php index aadc968370bfd..b9c0de5f4c52a 100644 --- a/lib/internal/Magento/Framework/EntityManager/Operation/Delete.php +++ b/lib/internal/Magento/Framework/EntityManager/Operation/Delete.php @@ -118,7 +118,7 @@ public function execute($entity, $arguments = []) $entity = $this->deleteMain->execute($entity, $arguments); $this->eventManager->dispatchEntityEvent($entityType, 'delete_after', ['entity' => $entity]); $this->eventManager->dispatch( - 'entity_manager_delete_before', + 'entity_manager_delete_after', [ 'entity_type' => $entityType, 'entity' => $entity diff --git a/lib/internal/Magento/Framework/EntityManager/Operation/ExtensionPool.php b/lib/internal/Magento/Framework/EntityManager/Operation/ExtensionPool.php index 703ed96ece4b9..5d3d37bb830d5 100644 --- a/lib/internal/Magento/Framework/EntityManager/Operation/ExtensionPool.php +++ b/lib/internal/Magento/Framework/EntityManager/Operation/ExtensionPool.php @@ -7,7 +7,6 @@ namespace Magento\Framework\EntityManager\Operation; use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\EntityManager\Operation\ExtensionInterface; /** * Class ExtensionPool diff --git a/lib/internal/Magento/Framework/EntityManager/OperationPool.php b/lib/internal/Magento/Framework/EntityManager/OperationPool.php index 97d580e328b1b..90a5acb8b8a03 100644 --- a/lib/internal/Magento/Framework/EntityManager/OperationPool.php +++ b/lib/internal/Magento/Framework/EntityManager/OperationPool.php @@ -7,7 +7,6 @@ namespace Magento\Framework\EntityManager; use Magento\Framework\ObjectManagerInterface as ObjectManager; -use Magento\Framework\EntityManager\OperationInterface; use Magento\Framework\EntityManager\Operation\CheckIfExists; use Magento\Framework\EntityManager\Operation\Read; use Magento\Framework\EntityManager\Operation\Create; diff --git a/lib/internal/Magento/Framework/EntityManager/Sequence/SequenceManager.php b/lib/internal/Magento/Framework/EntityManager/Sequence/SequenceManager.php index 25af4743ac9b8..7d92c05d6c830 100644 --- a/lib/internal/Magento/Framework/EntityManager/Sequence/SequenceManager.php +++ b/lib/internal/Magento/Framework/EntityManager/Sequence/SequenceManager.php @@ -68,7 +68,7 @@ public function force($entityType, $identifier) if (!isset($sequenceInfo['sequenceTable'])) { throw new \Exception( - 'TODO: use correct Exception class' . PHP_EOL . ' Sequence table doesnt exists' + 'TODO: use correct Exception class' . PHP_EOL . ' Sequence table doesn\'t exists' ); } @@ -101,7 +101,7 @@ public function delete($entityType, $identifier) $metadata = $this->metadataPool->getMetadata($entityType); $sequenceInfo = $this->sequenceRegistry->retrieve($entityType); if (!isset($sequenceInfo['sequenceTable'])) { - throw new \Exception('TODO: use correct Exception class' . PHP_EOL . ' Sequence table doesnt exists'); + throw new \Exception('TODO: use correct Exception class' . PHP_EOL . ' Sequence table doesn\'t exists'); } try { $connection = $this->appResource->getConnectionByName($metadata->getEntityConnectionName()); diff --git a/lib/internal/Magento/Framework/Event.php b/lib/internal/Magento/Framework/Event.php index 4c116d0a33629..c7b15a8eb0722 100644 --- a/lib/internal/Magento/Framework/Event.php +++ b/lib/internal/Magento/Framework/Event.php @@ -88,7 +88,7 @@ public function dispatch() */ public function getName() { - return isset($this->_data['name']) ? $this->_data['name'] : null; + return $this->_data['name'] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Event/Test/Unit/ObserverTest.php b/lib/internal/Magento/Framework/Event/Test/Unit/ObserverTest.php index 5e5c60e149837..378de874974c6 100644 --- a/lib/internal/Magento/Framework/Event/Test/Unit/ObserverTest.php +++ b/lib/internal/Magento/Framework/Event/Test/Unit/ObserverTest.php @@ -8,8 +8,6 @@ use \Magento\Framework\Event\Observer; -use Magento\Framework\Event; - /** * Class ConfigTest * diff --git a/lib/internal/Magento/Framework/File/Size.php b/lib/internal/Magento/Framework/File/Size.php index 9817b3b746303..c5a51ec1760e7 100644 --- a/lib/internal/Magento/Framework/File/Size.php +++ b/lib/internal/Magento/Framework/File/Size.php @@ -36,7 +36,7 @@ class Size */ public function getPostMaxSize() { - return $this->_iniget('post_max_size'); + return $this->_iniGet('post_max_size'); } /** @@ -46,7 +46,7 @@ public function getPostMaxSize() */ public function getUploadMaxSize() { - return $this->_iniget('upload_max_filesize'); + return $this->_iniGet('upload_max_filesize'); } /** diff --git a/lib/internal/Magento/Framework/File/Uploader.php b/lib/internal/Magento/Framework/File/Uploader.php index c86f98c4e413a..33f458d2082e1 100644 --- a/lib/internal/Magento/Framework/File/Uploader.php +++ b/lib/internal/Magento/Framework/File/Uploader.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\File; -use Magento\Framework\Filesystem\DriverInterface; - /** * File upload class * diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php new file mode 100644 index 0000000000000..fe0e6b37666b7 --- /dev/null +++ b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Filesystem\Directory; + +use Magento\Framework\Exception\ValidatorException; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Phrase; + +/** + * @inheritDoc + * + * Validates paths using driver. + */ +class PathValidator implements PathValidatorInterface +{ + /** + * @var DriverInterface + */ + private $driver; + + /** + * @param DriverInterface $driver + */ + public function __construct(DriverInterface $driver) + { + $this->driver = $driver; + } + + /** + * @inheritDoc + */ + public function validate( + string $directoryPath, + string $path, + ?string $scheme = null, + bool $absolutePath = false + ): void { + $realDirectoryPath = $this->driver->getRealPathSafety($directoryPath); + if ($realDirectoryPath[-1] !== DIRECTORY_SEPARATOR) { + $realDirectoryPath .= DIRECTORY_SEPARATOR; + } + if (!$absolutePath) { + $actualPath = $this->driver->getRealPathSafety( + $this->driver->getAbsolutePath( + $realDirectoryPath, + $path, + $scheme + ) + ); + } else { + $actualPath = $this->driver->getRealPathSafety($path); + } + + if (mb_strpos($actualPath, $realDirectoryPath) !== 0 + && $path .DIRECTORY_SEPARATOR !== $realDirectoryPath + ) { + throw new ValidatorException( + new Phrase( + 'Path "%1" cannot be used with directory "%2"', + [$path, $directoryPath] + ) + ); + } + } +} diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/PathValidatorInterface.php b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidatorInterface.php new file mode 100644 index 0000000000000..ecb7da9aaeab6 --- /dev/null +++ b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidatorInterface.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Filesystem\Directory; + +use Magento\Framework\Exception\ValidatorException; + +/** + * Validate paths to be used with directories. + */ +interface PathValidatorInterface +{ + /** + * Validate if path can be used with a directory. + * + * @param string $directoryPath + * @param string $path + * @param string|null $scheme + * @param bool $absolutePath Is given path an absolute path?. + * @throws ValidatorException + * + * @return void + */ + public function validate( + string $directoryPath, + string $path, + ?string $scheme = null, + bool $absolutePath = false + ): void; +} diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php index 18c93f2f4e1c8..a3a4cec59953f 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php @@ -6,6 +6,7 @@ namespace Magento\Framework\Filesystem\Directory; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\ValidatorException; /** * @api @@ -33,21 +34,52 @@ class Read implements ReadInterface */ protected $driver; + /** + * @var PathValidatorInterface|null + */ + private $pathValidator; + /** * Constructor. Set properties. * * @param \Magento\Framework\Filesystem\File\ReadFactory $fileFactory * @param \Magento\Framework\Filesystem\DriverInterface $driver * @param string $path + * @param PathValidatorInterface|null $pathValidator */ public function __construct( \Magento\Framework\Filesystem\File\ReadFactory $fileFactory, \Magento\Framework\Filesystem\DriverInterface $driver, - $path + $path, + ?PathValidatorInterface $pathValidator = null ) { $this->fileFactory = $fileFactory; $this->driver = $driver; $this->setPath($path); + $this->pathValidator = $pathValidator; + } + + /** + * @param null|string $path + * @param null|string $scheme + * @param bool $absolutePath + * @throws ValidatorException + * + * @return void + */ + protected function validatePath( + ?string $path, + ?string $scheme = null, + bool $absolutePath = false + ): void { + if ($path && $this->pathValidator) { + $this->pathValidator->validate( + $this->path, + $path, + $scheme, + $absolutePath + ); + } } /** @@ -69,10 +101,13 @@ protected function setPath($path) * * @param string $path * @param string $scheme + * @throws ValidatorException * @return string */ public function getAbsolutePath($path = null, $scheme = null) { + $this->validatePath($path, $scheme); + return $this->driver->getAbsolutePath($this->path, $path, $scheme); } @@ -80,10 +115,17 @@ public function getAbsolutePath($path = null, $scheme = null) * Retrieves relative path * * @param string $path + * @throws ValidatorException * @return string */ public function getRelativePath($path = null) { + $this->validatePath( + $path, + null, + $path && $path[0] === DIRECTORY_SEPARATOR + ); + return $this->driver->getRelativePath($this->path, $path); } @@ -91,10 +133,13 @@ public function getRelativePath($path = null) * Retrieve list of all entities in given path * * @param string|null $path + * @throws ValidatorException * @return string[] */ public function read($path = null) { + $this->validatePath($path); + $files = $this->driver->readDirectory($this->driver->getAbsolutePath($this->path, $path)); $result = []; foreach ($files as $file) { @@ -107,10 +152,13 @@ public function read($path = null) * Read recursively * * @param null $path + * @throws ValidatorException * @return string[] */ public function readRecursively($path = null) { + $this->validatePath($path); + $result = []; $paths = $this->driver->readDirectoryRecursively($this->driver->getAbsolutePath($this->path, $path)); /** @var \FilesystemIterator $file */ @@ -126,10 +174,13 @@ public function readRecursively($path = null) * * @param string $pattern * @param string $path [optional] + * @throws ValidatorException * @return string[] */ public function search($pattern, $path = null) { + $this->validatePath($path); + if ($path) { $absolutePath = $this->driver->getAbsolutePath($this->path, $this->getRelativePath($path)); } else { @@ -150,9 +201,12 @@ public function search($pattern, $path = null) * @param string $path [optional] * @return bool * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function isExist($path = null) { + $this->validatePath($path); + return $this->driver->isExists($this->driver->getAbsolutePath($this->path, $path)); } @@ -162,9 +216,12 @@ public function isExist($path = null) * @param string $path * @return array * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function stat($path) { + $this->validatePath($path); + return $this->driver->stat($this->driver->getAbsolutePath($this->path, $path)); } @@ -174,9 +231,12 @@ public function stat($path) * @param string $path [optional] * @return bool * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function isReadable($path = null) { + $this->validatePath($path); + return $this->driver->isReadable($this->driver->getAbsolutePath($this->path, $path)); } @@ -184,11 +244,14 @@ public function isReadable($path = null) * Open file in read mode * * @param string $path + * @throws ValidatorException * * @return \Magento\Framework\Filesystem\File\ReadInterface */ public function openFile($path) { + $this->validatePath($path); + return $this->fileFactory->create( $this->driver->getAbsolutePath($this->path, $path), $this->driver @@ -203,9 +266,12 @@ public function openFile($path) * @param resource|null $context * @return string * @throws FileSystemException + * @throws ValidatorException */ public function readFile($path, $flag = null, $context = null) { + $this->validatePath($path); + $absolutePath = $this->driver->getAbsolutePath($this->path, $path); return $this->driver->fileGetContents($absolutePath, $flag, $context); } @@ -214,10 +280,13 @@ public function readFile($path, $flag = null, $context = null) * Check whether given path is file * * @param string $path + * @throws ValidatorException * @return bool */ public function isFile($path) { + $this->validatePath($path); + return $this->driver->isFile($this->driver->getAbsolutePath($this->path, $path)); } @@ -225,10 +294,13 @@ public function isFile($path) * Check whether given path is directory * * @param string $path [optional] + * @throws ValidatorException * @return bool */ public function isDirectory($path = null) { + $this->validatePath($path); + return $this->driver->isDirectory($this->driver->getAbsolutePath($this->path, $path)); } } diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/ReadFactory.php b/lib/internal/Magento/Framework/Filesystem/Directory/ReadFactory.php index a9fe7dcb7bf06..25a290455dc46 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/ReadFactory.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/ReadFactory.php @@ -36,7 +36,15 @@ public function __construct(DriverPool $driverPool) public function create($path, $driverCode = DriverPool::FILE) { $driver = $this->driverPool->getDriver($driverCode); - $factory = new \Magento\Framework\Filesystem\File\ReadFactory($this->driverPool); - return new Read($factory, $driver, $path); + $factory = new \Magento\Framework\Filesystem\File\ReadFactory( + $this->driverPool + ); + + return new Read( + $factory, + $driver, + $path, + new PathValidator($driver) + ); } } diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php index 900d8423b31d8..78732d73456f7 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php @@ -7,6 +7,7 @@ namespace Magento\Framework\Filesystem\Directory; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\ValidatorException; class Write extends Read implements WriteInterface { @@ -24,16 +25,16 @@ class Write extends Read implements WriteInterface * @param \Magento\Framework\Filesystem\DriverInterface $driver * @param string $path * @param int $createPermissions + * @param PathValidatorInterface|null $pathValidator */ public function __construct( \Magento\Framework\Filesystem\File\WriteFactory $fileFactory, \Magento\Framework\Filesystem\DriverInterface $driver, $path, - $createPermissions = null + $createPermissions = null, + ?PathValidatorInterface $pathValidator = null ) { - $this->fileFactory = $fileFactory; - $this->driver = $driver; - $this->setPath($path); + parent::__construct($fileFactory, $driver, $path, $pathValidator); if (null !== $createPermissions) { $this->permissions = $createPermissions; } @@ -80,9 +81,11 @@ protected function assertIsFile($path) * @param string $path * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function create($path = null) { + $this->validatePath($path); $absolutePath = $this->driver->getAbsolutePath($this->path, $path); if ($this->driver->isDirectory($absolutePath)) { return true; @@ -98,9 +101,11 @@ public function create($path = null) * @param WriteInterface $targetDirectory * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function renameFile($path, $newPath, WriteInterface $targetDirectory = null) { + $this->validatePath($path); $this->assertIsFile($path); $targetDirectory = $targetDirectory ?: $this; if (!$targetDirectory->isExist($this->driver->getParentDirectory($newPath))) { @@ -119,9 +124,11 @@ public function renameFile($path, $newPath, WriteInterface $targetDirectory = nu * @param WriteInterface $targetDirectory * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function copyFile($path, $destination, WriteInterface $targetDirectory = null) { + $this->validatePath($path); $this->assertIsFile($path); $targetDirectory = $targetDirectory ?: $this; @@ -142,9 +149,11 @@ public function copyFile($path, $destination, WriteInterface $targetDirectory = * @param WriteInterface $targetDirectory [optional] * @return bool * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function createSymlink($path, $destination, WriteInterface $targetDirectory = null) { + $this->validatePath($path); $targetDirectory = $targetDirectory ?: $this; $parentDirectory = $this->driver->getParentDirectory($destination); if (!$targetDirectory->isExist($parentDirectory)) { @@ -162,9 +171,11 @@ public function createSymlink($path, $destination, WriteInterface $targetDirecto * @param string $path * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function delete($path = null) { + $this->validatePath($path); if (!$this->isExist($path)) { return true; } @@ -184,10 +195,13 @@ public function delete($path = null) * @param int $permissions * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function changePermissions($path, $permissions) { + $this->validatePath($path); $absolutePath = $this->driver->getAbsolutePath($this->path, $path); + return $this->driver->changePermissions($absolutePath, $permissions); } @@ -199,10 +213,13 @@ public function changePermissions($path, $permissions) * @param int $filePermissions * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function changePermissionsRecursively($path, $dirPermissions, $filePermissions) { + $this->validatePath($path); $absolutePath = $this->driver->getAbsolutePath($this->path, $path); + return $this->driver->changePermissionsRecursively($absolutePath, $dirPermissions, $filePermissions); } @@ -213,9 +230,12 @@ public function changePermissionsRecursively($path, $dirPermissions, $filePermis * @param int|null $modificationTime * @return bool * @throws FileSystemException + * @throws ValidatorException */ public function touch($path, $modificationTime = null) { + $this->validatePath($path); + $folder = $this->driver->getParentDirectory($path); $this->create($folder); $this->assertWritable($folder); @@ -228,9 +248,12 @@ public function touch($path, $modificationTime = null) * @param null $path * @return bool * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function isWritable($path = null) { + $this->validatePath($path); + return $this->driver->isWritable($this->driver->getAbsolutePath($this->path, $path)); } @@ -241,13 +264,16 @@ public function isWritable($path = null) * @param string $mode * @return \Magento\Framework\Filesystem\File\WriteInterface * @throws \Magento\Framework\Exception\FileSystemException + * @throws ValidatorException */ public function openFile($path, $mode = 'w') { + $this->validatePath($path); $folder = dirname($path); $this->create($folder); $this->assertWritable($this->isExist($path) ? $path : $folder); $absolutePath = $this->driver->getAbsolutePath($this->path, $path); + return $this->fileFactory->create($absolutePath, $this->driver, $mode); } diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php b/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php index a723ed6a7bea6..ff14b12f62047 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/WriteFactory.php @@ -37,7 +37,16 @@ public function __construct(DriverPool $driverPool) public function create($path, $driverCode = DriverPool::FILE, $createPermissions = null) { $driver = $this->driverPool->getDriver($driverCode); - $factory = new \Magento\Framework\Filesystem\File\WriteFactory($this->driverPool); - return new Write($factory, $driver, $path, $createPermissions); + $factory = new \Magento\Framework\Filesystem\File\WriteFactory( + $this->driverPool + ); + + return new Write( + $factory, + $driver, + $path, + $createPermissions, + new PathValidator($driver) + ); } } diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/File.php b/lib/internal/Magento/Framework/Filesystem/Driver/File.php index 69382d66e349e..b54b02bd6de98 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/File.php @@ -950,6 +950,13 @@ public function getRealPathSafety($path) if (strpos($path, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) === false) { return $path; } + + //Removing redundant directory separators. + $path = preg_replace( + '/\\' .DIRECTORY_SEPARATOR .'\\' .DIRECTORY_SEPARATOR .'+/', + DIRECTORY_SEPARATOR, + $path + ); $pathParts = explode(DIRECTORY_SEPARATOR, $path); $realPath = []; foreach ($pathParts as $pathPart) { diff --git a/lib/internal/Magento/Framework/Filesystem/File/WriteFactory.php b/lib/internal/Magento/Framework/Filesystem/File/WriteFactory.php index 64683a104784d..a45d6a62488f6 100644 --- a/lib/internal/Magento/Framework/Filesystem/File/WriteFactory.php +++ b/lib/internal/Magento/Framework/Filesystem/File/WriteFactory.php @@ -8,7 +8,7 @@ use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Filesystem\DriverPool; -class WriteFactory +class WriteFactory extends ReadFactory { /** * Pool of filesystem drivers @@ -24,6 +24,7 @@ class WriteFactory */ public function __construct(DriverPool $driverPool) { + parent::__construct($driverPool); $this->driverPool = $driverPool; } diff --git a/lib/internal/Magento/Framework/Filesystem/Io/File.php b/lib/internal/Magento/Framework/Filesystem/Io/File.php index c1cfebc7a0ac1..8fec7f7630257 100644 --- a/lib/internal/Magento/Framework/Filesystem/Io/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Io/File.php @@ -233,7 +233,7 @@ public function streamStat($part = null, $default = null) } $stat = @fstat($this->_streamHandler); if ($part !== null) { - return isset($stat[$part]) ? $stat[$part] : $default; + return $stat[$part] ?? $default; } return $stat; } diff --git a/lib/internal/Magento/Framework/Filesystem/Io/Ftp.php b/lib/internal/Magento/Framework/Filesystem/Io/Ftp.php index 1aa9d61f8865a..04df5fd3f3a6c 100644 --- a/lib/internal/Magento/Framework/Filesystem/Io/Ftp.php +++ b/lib/internal/Magento/Framework/Filesystem/Io/Ftp.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Filesystem\Io; -use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Phrase; use Magento\Framework\Exception\LocalizedException; diff --git a/lib/internal/Magento/Framework/Filesystem/Io/IoInterface.php b/lib/internal/Magento/Framework/Filesystem/Io/IoInterface.php index c31d3ff9e52ba..93c85ebafe727 100644 --- a/lib/internal/Magento/Framework/Filesystem/Io/IoInterface.php +++ b/lib/internal/Magento/Framework/Filesystem/Io/IoInterface.php @@ -5,10 +5,9 @@ */ namespace Magento\Framework\Filesystem\Io; -use Magento\Framework\Filesystem\DriverInterface; - /** * Input/output client interface + * @api */ interface IoInterface { diff --git a/lib/internal/Magento/Framework/Filesystem/Test/Unit/File/ReadFactoryTest.php b/lib/internal/Magento/Framework/Filesystem/Test/Unit/File/ReadFactoryTest.php index c2115205e6bd2..93b12f8d56ff8 100644 --- a/lib/internal/Magento/Framework/Filesystem/Test/Unit/File/ReadFactoryTest.php +++ b/lib/internal/Magento/Framework/Filesystem/Test/Unit/File/ReadFactoryTest.php @@ -7,8 +7,6 @@ use \Magento\Framework\Filesystem\File\ReadFactory; -use Magento\Framework\Filesystem\DriverPool; - /** * Class ReadFactoryTest */ diff --git a/lib/internal/Magento/Framework/Filter/AbstractFactory.php b/lib/internal/Magento/Framework/Filter/AbstractFactory.php index 2a0ae0ba15e42..c30b07aa09061 100644 --- a/lib/internal/Magento/Framework/Filter/AbstractFactory.php +++ b/lib/internal/Magento/Framework/Filter/AbstractFactory.php @@ -68,7 +68,7 @@ public function canCreateFilter($alias) */ public function isShared($class) { - return isset($this->shared[$class]) ? $this->shared[$class] : $this->shareByDefault; + return $this->shared[$class] ?? $this->shareByDefault; } /** diff --git a/lib/internal/Magento/Framework/Filter/DataObject/Grid.php b/lib/internal/Magento/Framework/Filter/DataObject/Grid.php index 7676558a927b0..54dccc8ae5b82 100644 --- a/lib/internal/Magento/Framework/Filter/DataObject/Grid.php +++ b/lib/internal/Magento/Framework/Filter/DataObject/Grid.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Filter\DataObject; -use Magento\Framework\DataObject; - class Grid extends \Magento\Framework\Filter\DataObject { /** diff --git a/lib/internal/Magento/Framework/Filter/Factory.php b/lib/internal/Magento/Framework/Filter/Factory.php index a5e5978a83f06..dbe037ecbbbd1 100644 --- a/lib/internal/Magento/Framework/Filter/Factory.php +++ b/lib/internal/Magento/Framework/Filter/Factory.php @@ -32,6 +32,7 @@ class Factory extends AbstractFactory 'decrypt' => \Magento\Framework\Filter\Decrypt::class, 'translit' => \Magento\Framework\Filter\Translit::class, 'translitUrl' => \Magento\Framework\Filter\TranslitUrl::class, + 'truncateFilter' => \Magento\Framework\Filter\TruncateFilter::class, ]; /** diff --git a/lib/internal/Magento/Framework/Filter/FilterManager.php b/lib/internal/Magento/Framework/Filter/FilterManager.php index 52c9d017ad11c..ca5d998af833f 100644 --- a/lib/internal/Magento/Framework/Filter/FilterManager.php +++ b/lib/internal/Magento/Framework/Filter/FilterManager.php @@ -21,6 +21,7 @@ * @method string removeTags(string $value, $params = array()) * @method string stripTags(string $value, $params = array()) * @method string truncate(string $value, $params = array()) + * @method string truncateFilter(string $value, $params = array()) * @method string encrypt(string $value, $params = array()) * @method string decrypt(string $value, $params = array()) * @method string translit(string $value) diff --git a/lib/internal/Magento/Framework/Filter/Input.php b/lib/internal/Magento/Framework/Filter/Input.php index 39c7a54786edb..6da748fcbeb9f 100644 --- a/lib/internal/Magento/Framework/Filter/Input.php +++ b/lib/internal/Magento/Framework/Filter/Input.php @@ -183,7 +183,7 @@ public function getFilters($name = null) if (null === $name) { return $this->_filters; } else { - return isset($this->_filters[$name]) ? $this->_filters[$name] : null; + return $this->_filters[$name] ?? null; } } diff --git a/lib/internal/Magento/Framework/Filter/Translit.php b/lib/internal/Magento/Framework/Filter/Translit.php index 7a84a6e33af18..a6162aa7a7fff 100644 --- a/lib/internal/Magento/Framework/Filter/Translit.php +++ b/lib/internal/Magento/Framework/Filter/Translit.php @@ -409,7 +409,7 @@ public function __construct(\Magento\Framework\App\Config\ScopeConfigInterface $ $convertConfig = $config->getValue('url/convert', 'default'); if ($convertConfig) { foreach ($convertConfig as $configValue) { - $this->convertTable[strval($configValue['from'])] = strval($configValue['to']); + $this->convertTable[(string)$configValue['from']] = (string)$configValue['to']; } } } diff --git a/lib/internal/Magento/Framework/Filter/Truncate.php b/lib/internal/Magento/Framework/Filter/Truncate.php index fd4fbe9910427..a4dd35b302705 100644 --- a/lib/internal/Magento/Framework/Filter/Truncate.php +++ b/lib/internal/Magento/Framework/Filter/Truncate.php @@ -10,6 +10,9 @@ * * Truncate a string to a certain length if necessary, appending the $etc string. * $remainder will contain the string that has been replaced with $etc. + * + * @deprecated + * @see \Magento\Framework\Filter\TruncateFilter */ class Truncate implements \Zend_Filter_Interface { diff --git a/lib/internal/Magento/Framework/Filter/TruncateFilter.php b/lib/internal/Magento/Framework/Filter/TruncateFilter.php new file mode 100644 index 0000000000000..a41469bb6f2a9 --- /dev/null +++ b/lib/internal/Magento/Framework/Filter/TruncateFilter.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Filter; + +use Magento\Framework\Filter\TruncateFilter\Result; +use Magento\Framework\Filter\TruncateFilter\ResultFactory; + +/** + * Truncate filter + * + * Truncate a string to a certain length if necessary, appending the $etc string. + * $remainder will contain the string that has been replaced with $etc. + */ +class TruncateFilter implements \Zend_Filter_Interface +{ + /** + * @var int + */ + private $length; + + /** + * @var string + */ + private $etc; + + /** + * @var bool + */ + private $breakWords; + + /** + * @var \Magento\Framework\Stdlib\StringUtils + */ + private $stringUtils; + + /** + * @var ResultFactory + */ + private $resultFactory; + + /** + * @param \Magento\Framework\Stdlib\StringUtils $stringUtils + * @param ResultFactory $resultFactory + * @param int $length + * @param string $etc + * @param bool $breakWords + */ + public function __construct( + \Magento\Framework\Stdlib\StringUtils $stringUtils, + ResultFactory $resultFactory, + $length = 80, + $etc = '...', + $breakWords = true + ) { + $this->stringUtils = $stringUtils; + $this->resultFactory = $resultFactory; + $this->length = $length; + $this->etc = $etc; + $this->breakWords = $breakWords; + } + + /** + * Filter value + * + * @param string $string + * @return Result + */ + public function filter($string) : Result + { + /** @var Result $result */ + $result = $this->resultFactory->create(['value' => $string, 'remainder' => '']); + $length = $this->length; + if (0 == $length) { + $result->setValue(''); + return $result; + } + + $originalLength = $this->stringUtils->strlen($string); + if ($originalLength > $length) { + $length -= $this->stringUtils->strlen($this->etc); + if ($length <= 0) { + $result->setValue(''); + return $result; + } + $preparedString = $string; + $preparedLength = $length; + if (!$this->breakWords) { + $preparedString = preg_replace( + '/\s+?(\S+)?$/u', + '', + $this->stringUtils->substr($string, 0, $length + 1) + ); + $preparedLength = $this->stringUtils->strlen($preparedString); + } + $result->setRemainder($this->stringUtils->substr($string, $preparedLength, $originalLength)); + $result->setValue($this->stringUtils->substr($preparedString, 0, $length) . $this->etc); + return $result; + } + + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Filter/TruncateFilter/Result.php b/lib/internal/Magento/Framework/Filter/TruncateFilter/Result.php new file mode 100644 index 0000000000000..c1ee6be6dadf5 --- /dev/null +++ b/lib/internal/Magento/Framework/Filter/TruncateFilter/Result.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Filter\TruncateFilter; + +class Result +{ + /** + * @var string + */ + private $value; + + /** + * @var string + */ + private $remainder; + + /** + * Result constructor. + * @param string $value + * @param string $remainder + */ + public function __construct(string $value, string $remainder) + { + $this->value = $value; + $this->remainder = $remainder; + } + + /** + * Set result value + * + * @param string $value + * @return void + */ + public function setValue(string $value) : void + { + $this->value = $value; + } + + /** + * Get value + * + * @return string + */ + public function getValue() : string + { + return $this->value; + } + + /** + * Set remainder + * + * @param string $remainder + * @return void + */ + public function setRemainder(string $remainder) : void + { + $this->remainder = $remainder; + } + + /** + * Get remainder + * + * @return string + */ + public function getRemainder() : string + { + return $this->remainder; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php b/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php index 668ec2bdc84e4..63fef73186b12 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php @@ -56,6 +56,7 @@ public function generate() : Schema $schema = $this->schemaFactory->create( [ 'query' => $this->outputMapper->getOutputType('Query'), + 'mutation' => $this->outputMapper->getOutputType('Mutation'), 'typeLoader' => function ($name) { return $this->outputMapper->getOutputType($name); }, diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/README.md b/lib/internal/Magento/Framework/GraphQlSchemaStitching/README.md index da47fff7f96d4..b87cd2d7e4e7c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/README.md +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/README.md @@ -1 +1 @@ -The GraphQlSchemaStitching library contains functionality for processing GraphQl SDL schema, to be used by Graphql library to build objects fro the data generated. +The GraphQlSchemaStitching library contains functionality for processing GraphQl SDL schema, to be used by Graphql library to build objects from the data generated. diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index 93274b1d063bf..ea21faf3f340d 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -106,7 +106,7 @@ protected function _getImageNeedMemorySize($file) } return round( - ($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $imageInfo['channels'] / 8 + Pow(2, 16)) * 1.65 + ($imageInfo[0] * $imageInfo[1] * $imageInfo['bits'] * $imageInfo['channels'] / 8 + pow(2, 16)) * 1.65 ); } @@ -426,7 +426,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = imagecolortransparent($newWatermark, $col); imagefilledrectangle($newWatermark, 0, 0, $this->getWatermarkWidth(), $this->getWatermarkHeight(), $col); imagealphablending($newWatermark, true); - imageSaveAlpha($newWatermark, true); + imagesavealpha($newWatermark, true); imagecopyresampled( $newWatermark, $watermark, @@ -451,7 +451,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = imagecolortransparent($newWatermark, $col); imagefilledrectangle($newWatermark, 0, 0, $this->_imageSrcWidth, $this->_imageSrcHeight, $col); imagealphablending($newWatermark, true); - imageSaveAlpha($newWatermark, true); + imagesavealpha($newWatermark, true); imagecopyresampled( $newWatermark, $watermark, @@ -669,7 +669,7 @@ private function imageDestroy() private function _saveAlpha($imageHandler) { $background = imagecolorallocate($imageHandler, 0, 0, 0); - ImageColorTransparent($imageHandler, $background); + imagecolortransparent($imageHandler, $background); imagealphablending($imageHandler, false); imagesavealpha($imageHandler, true); } diff --git a/lib/internal/Magento/Framework/Indexer/Action/Base.php b/lib/internal/Magento/Framework/Indexer/Action/Base.php index 11cf1cec62636..636335192cbc5 100644 --- a/lib/internal/Magento/Framework/Indexer/Action/Base.php +++ b/lib/internal/Magento/Framework/Indexer/Action/Base.php @@ -221,7 +221,7 @@ protected function prepareDataSource(array $ids = []) { return !count($ids) ? $this->createResultCollection() - : $this->createResultCollection()->addFieldToFilter($this->getPrimaryResource()->getIdFieldname(), $ids); + : $this->createResultCollection()->addFieldToFilter($this->getPrimaryResource()->getIdFieldName(), $ids); } /** diff --git a/lib/internal/Magento/Framework/Indexer/CacheContext.php b/lib/internal/Magento/Framework/Indexer/CacheContext.php index 7a56b320859e9..4a6964477ebd5 100644 --- a/lib/internal/Magento/Framework/Indexer/CacheContext.php +++ b/lib/internal/Magento/Framework/Indexer/CacheContext.php @@ -30,7 +30,6 @@ class CacheContext implements \Magento\Framework\DataObject\IdentityInterface public function registerEntities($cacheTag, $ids) { $this->entities[$cacheTag] = array_merge($this->getRegisteredEntity($cacheTag), $ids); - return $this; } @@ -40,7 +39,7 @@ public function registerEntities($cacheTag, $ids) * @param array $cacheTags * @return $this */ - public function registerTags(array $cacheTags) + public function registerTags($cacheTags) { $this->tags = array_merge($this->tags, $cacheTags); return $this; diff --git a/lib/internal/Magento/Framework/Indexer/Config/Reader.php b/lib/internal/Magento/Framework/Indexer/Config/Reader.php index 6ef22b3b7f796..9ed35ef0e9af5 100644 --- a/lib/internal/Magento/Framework/Indexer/Config/Reader.php +++ b/lib/internal/Magento/Framework/Indexer/Config/Reader.php @@ -18,6 +18,7 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem '/config/indexer/source' => 'name', '/config/indexer/fieldset' => 'name', '/config/indexer/fieldset/field' => 'name', + '/config/indexer/dependencies/indexer' => 'id', ]; /** diff --git a/lib/internal/Magento/Framework/Indexer/Dimension.php b/lib/internal/Magento/Framework/Indexer/Dimension.php index 4cb74003c46fc..dacc8d7f524f5 100644 --- a/lib/internal/Magento/Framework/Indexer/Dimension.php +++ b/lib/internal/Magento/Framework/Indexer/Dimension.php @@ -14,6 +14,16 @@ */ class Dimension { + /** + * @var string + */ + private $name; + + /** + * @var string + */ + private $value; + /** * @param string $name * @param string $value diff --git a/lib/internal/Magento/Framework/Indexer/DimensionFactory.php b/lib/internal/Magento/Framework/Indexer/DimensionFactory.php index a137e2d0e94a6..8eaeff5628d60 100644 --- a/lib/internal/Magento/Framework/Indexer/DimensionFactory.php +++ b/lib/internal/Magento/Framework/Indexer/DimensionFactory.php @@ -34,11 +34,14 @@ public function __construct(ObjectManagerInterface $objectManager) * @param string $value * @return Dimension */ - public function create(string $name, $value): Dimension + public function create(string $name, string $value): Dimension { - return $this->objectManager->create(Dimension::class, [ - 'name' => $name, - 'value' => (string) $value, - ]); + return $this->objectManager->create( + Dimension::class, + [ + 'name' => $name, + 'value' => $value, + ] + ); } } diff --git a/lib/internal/Magento/Framework/Indexer/Dimension/DimensionProviderInterface.php b/lib/internal/Magento/Framework/Indexer/DimensionProviderInterface.php similarity index 90% rename from lib/internal/Magento/Framework/Indexer/Dimension/DimensionProviderInterface.php rename to lib/internal/Magento/Framework/Indexer/DimensionProviderInterface.php index e2d05b2ae038f..ea4f56eb48d41 100644 --- a/lib/internal/Magento/Framework/Indexer/Dimension/DimensionProviderInterface.php +++ b/lib/internal/Magento/Framework/Indexer/DimensionProviderInterface.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\Framework\Indexer\Dimension; +namespace Magento\Framework\Indexer; /** * @api diff --git a/lib/internal/Magento/Framework/Indexer/Dimension/DimensionalIndexerInterface.php b/lib/internal/Magento/Framework/Indexer/DimensionalIndexerInterface.php similarity index 55% rename from lib/internal/Magento/Framework/Indexer/Dimension/DimensionalIndexerInterface.php rename to lib/internal/Magento/Framework/Indexer/DimensionalIndexerInterface.php index e2f68272565a6..43c4e7a7fd70b 100644 --- a/lib/internal/Magento/Framework/Indexer/Dimension/DimensionalIndexerInterface.php +++ b/lib/internal/Magento/Framework/Indexer/DimensionalIndexerInterface.php @@ -5,11 +5,11 @@ */ declare(strict_types=1); -namespace Magento\Framework\Indexer\Dimension; +namespace Magento\Framework\Indexer; /** * @api - * Run indexer by specific dimension + * Run indexer by dimensions */ interface DimensionalIndexerInterface { @@ -17,9 +17,9 @@ interface DimensionalIndexerInterface * Execute indexer by specified dimension. * Accept array of dimensions DTO that represent indexer dimension * - * @param \Magento\Framework\Indexer\Dimension[] $dimension - * @param \Traversable|null $entityIds + * @param \Magento\Framework\Indexer\Dimension[] $dimensions + * @param \Traversable $entityIds * @return void */ - public function executeByDimension(array $dimension, \Traversable $entityIds = null); + public function executeByDimensions(array $dimensions, \Traversable $entityIds); } diff --git a/lib/internal/Magento/Framework/Indexer/MultiDimensionProvider.php b/lib/internal/Magento/Framework/Indexer/MultiDimensionProvider.php new file mode 100644 index 0000000000000..a02637f755b89 --- /dev/null +++ b/lib/internal/Magento/Framework/Indexer/MultiDimensionProvider.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Indexer; + +/** + * Multiply dimensions from provided DimensionProviderInterface + */ +class MultiDimensionProvider implements \IteratorAggregate +{ + /** + * @var array + */ + private $dimensionsIterators = []; + + /** + * @var array + */ + private $dimensionsDataProviders = []; + + /** + * @var int + */ + private $dimensionsProvidersCount = 0; + + /** + * @param DimensionProviderInterface[] $dimensionProviders + */ + public function __construct(array $dimensionProviders = []) + { + foreach ($dimensionProviders as $dimensionDataProvider) { + $this->addDimensionDataProvider($dimensionDataProvider); + } + } + + /** + * Returns generator that will return multiplied dimensions on each iteration + * + * @return \Traversable|Dimension[][] + * @throws \LogicException + */ + public function getIterator(): \Traversable + { + // just return empty array if we have no dimension providers to iterate over + if ($this->dimensionsProvidersCount === 0) { + yield []; + return; + } + + // this recreates iterators for dimension so we can iterate over them + $this->rewind(); + + // if at leas one dimension provider has no dimensions to return we can't multiple dimension at all + if (!$this->hasCurrentDimension()) { + throw new \LogicException('Can`t multiple dimensions because some of them are empty.'); + } + + // return dimensions until all iterators become invalid + while ($this->hasCurrentDimension()) { + yield $this->getCurrentDimension(); + $this->setNextDimension(); + } + } + + /** + * Return all dimensions for current state of each dimension provider + * + * @return array + */ + private function getCurrentDimension(): array + { + $dimensions = []; + + foreach ($this->dimensionsIterators as $dimensionIterator) { + /** @var Dimension $dimension */ + $dimension = $dimensionIterator->current(); + $dimensions[$dimension->getName()] = $dimension; + } + + return $dimensions; + } + + /** + * Iterates over dimension iterators one by one starting from right to left + * This approach emulates iterations over X nested foreach loops e.g.: + * + * @return void + */ + private function setNextDimension() + { + $this->dimensionsIterators[$this->dimensionsProvidersCount - 1]->next(); + + for ($i = ($this->dimensionsProvidersCount - 1); $i > 0; $i--) { + if (!$this->dimensionsIterators[$i]->valid()) { + $this->dimensionsIterators[$i] = $this->dimensionsDataProviders[$i]->getIterator(); + $this->dimensionsIterators[$i-1]->next(); + } + } + } + + /** + * Recreates iterators so all MultiDimensionProvider can be iterated again + * + * @return void + */ + private function rewind() + { + $this->dimensionsIterators = []; + + foreach ($this->dimensionsDataProviders as $dimensionDataProvider) { + $this->dimensionsIterators[] = $dimensionDataProvider->getIterator(); + } + } + + /** + * Check if all dimension iterators are in valid state + * + * If at least one of dimension iterators is invalid before very first iteration - we assume + * that dimension provider has no dimensions at all, which means we can't multiple all dimensions + * + * If all dimension iterators became invalid - we assume that multiplication is already done + * + * @return bool + */ + private function hasCurrentDimension(): bool + { + $valid = true; + + foreach ($this->dimensionsIterators as $dimensionsIterator) { + // if at least one data provider is invalid at this stage - all generator is invalid + if (!$dimensionsIterator->valid()) { + return false; + } + } + + // generator is valid only when all data providers are valid + return $valid; + } + + /** + * Collects dimension data providers + * This was done via separate method to ensure that each provider has required interface + * + * @param DimensionProviderInterface $dimensionDataProvider + * @return void + */ + private function addDimensionDataProvider(DimensionProviderInterface $dimensionDataProvider) + { + $this->dimensionsDataProviders[] = $dimensionDataProvider; + $this->dimensionsProvidersCount++; + } +} diff --git a/lib/internal/Magento/Framework/Indexer/SaveHandler/IndexerHandler.php b/lib/internal/Magento/Framework/Indexer/SaveHandler/IndexerHandler.php index 3908cf1e98762..c3ab971c992e6 100644 --- a/lib/internal/Magento/Framework/Indexer/SaveHandler/IndexerHandler.php +++ b/lib/internal/Magento/Framework/Indexer/SaveHandler/IndexerHandler.php @@ -12,7 +12,6 @@ use Magento\Framework\Indexer\IndexStructureInterface; use Magento\Framework\Indexer\ScopeResolver\FlatScopeResolver; use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver; -use Magento\Framework\Indexer\SaveHandler\Batch; class IndexerHandler implements IndexerInterface { diff --git a/lib/internal/Magento/Framework/Indexer/SaveHandlerFactory.php b/lib/internal/Magento/Framework/Indexer/SaveHandlerFactory.php index 82e8a26553b89..80231dd7aa290 100644 --- a/lib/internal/Magento/Framework/Indexer/SaveHandlerFactory.php +++ b/lib/internal/Magento/Framework/Indexer/SaveHandlerFactory.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\Indexer; -use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Indexer\SaveHandler\IndexerInterface as SaveHandlerInterface; diff --git a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php index 891a6cd1efe12..a68de6ad36f9a 100644 --- a/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php +++ b/lib/internal/Magento/Framework/Indexer/ScopeResolver/IndexScopeResolver.php @@ -42,16 +42,18 @@ public function __construct( */ public function resolve($index, array $dimensions) { - $tableNameParts = [$index]; + $tableNameParts = []; foreach ($dimensions as $dimension) { switch ($dimension->getName()) { case 'scope': - $tableNameParts[] = $dimension->getName() . $this->getScopeId($dimension); + $tableNameParts[$dimension->getName()] = $dimension->getName() . $this->getScopeId($dimension); break; default: - $tableNameParts[] = $dimension->getName() . $dimension->getValue(); + $tableNameParts[$dimension->getName()] = $dimension->getName() . $dimension->getValue(); } } + ksort($tableNameParts); + array_unshift($tableNameParts, $index); return $this->resource->getTableName(implode('_', $tableNameParts)); } diff --git a/lib/internal/Magento/Framework/Indexer/StructureFactory.php b/lib/internal/Magento/Framework/Indexer/StructureFactory.php index f9b7eafdfc436..1b6729e4603ec 100644 --- a/lib/internal/Magento/Framework/Indexer/StructureFactory.php +++ b/lib/internal/Magento/Framework/Indexer/StructureFactory.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Indexer; -use Magento\Framework\Indexer\IndexStructureInterface; - class StructureFactory { /** diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php index 514bd673f8525..2c0d9124b9603 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/IndexStructureTest.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Indexer\Test\Unit; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Ddl\Table; use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/MultiDimensionProviderTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/MultiDimensionProviderTest.php new file mode 100644 index 0000000000000..b55ace9bdec3d --- /dev/null +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/MultiDimensionProviderTest.php @@ -0,0 +1,256 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Indexer\Test\Unit; + +use \Magento\Framework\Indexer\MultiDimensionProvider; +use \Magento\Framework\Indexer\DimensionProviderInterface; +use \Magento\Framework\Indexer\Dimension; + +class MultiDimensionProviderTest extends \PHPUnit\Framework\TestCase +{ + /** + * tests that MultiDimensionProvider will return [[]] in case it has no dimension providers + */ + public function testWithNoDataProviders() + { + // prepare expected dimensions + $expectedDimensions = [[]]; + + // collect actual dimensions + $multiDimensionProvider = new MultiDimensionProvider([]); + + $actualDimensions = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions[] = $dimension; + } + + $this->assertSame($expectedDimensions, $actualDimensions); + } + + /** + * tests multiplication of dimensions from different providers + * + * e.g we have three dimensions: + * - dimension X with values (x1, x2) + * - dimension Y with values (y1, y2, y3) + * - dimension Z with values (z1, z2) + * + * the multiplication result set will be: + * x1-y1-z1 + * x1-y1-z2 + * x1-y2-z1 + * x1-y2-z2 + * x1-y3-z1 + * x1-y3-z2 + * x2-y1-z1 + * x2-y1-z2 + * x2-y2-z1 + * x2-y2-z2 + * x2-y3-z1 + * x2-y3-z2 + */ + public function testWithMultipleDataProviders() + { + // prepare expected dimensions + $dimensionXData = [ + $this->getDimensionMock('x', '1'), + $this->getDimensionMock('x', '2'), + $this->getDimensionMock('x', '3'), + ]; + + $dimensionYData = [ + $this->getDimensionMock('y', '1'), + $this->getDimensionMock('y', '2'), + $this->getDimensionMock('y', '3'), + $this->getDimensionMock('y', '4'), + $this->getDimensionMock('y', '5'), + ]; + + $dimensionZData = [ + $this->getDimensionMock('z', '1'), + $this->getDimensionMock('z', '2'), + ]; + + $expectedDimensions = []; + + foreach ($dimensionXData as $dimensionX) { + foreach ($dimensionYData as $dimensionY) { + foreach ($dimensionZData as $dimensionZ) { + $expectedDimensions[] = [ + $dimensionX->getName() => $dimensionX, + $dimensionY->getName() => $dimensionY, + $dimensionZ->getName() => $dimensionZ, + ]; + } + } + } + + // collect actual dimensions + $multiDimensionProvider = new MultiDimensionProvider( + [ + $this->getDimensionProviderMock($dimensionXData), + $this->getDimensionProviderMock($dimensionYData), + $this->getDimensionProviderMock($dimensionZData), + ] + ); + + $actualDimensions = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions[] = $dimension; + } + + $this->assertSame($expectedDimensions, $actualDimensions); + } + + /** + * tests that the same MultiDimensionProvider can be used in foreach multiple times without creating again + */ + public function testMultiDimensionProviderIsReIterable() + { + // prepare expected dimensions + $dimensionXData = [ + $this->getDimensionMock('x', '1'), + $this->getDimensionMock('x', '2'), + $this->getDimensionMock('x', '3'), + ]; + + $dimensionZData = [ + $this->getDimensionMock('z', '1'), + $this->getDimensionMock('z', '2'), + ]; + + // collect actual dimensions + $multiDimensionProvider = new MultiDimensionProvider( + [ + $this->getDimensionProviderMock($dimensionXData), + $this->getDimensionProviderMock($dimensionZData), + ] + ); + + // first iteration + $actualDimensions1st = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions1st[] = $dimension; + } + + // second iteration + $actualDimensions2nd = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions2nd[] = $dimension; + } + + $this->assertSame($actualDimensions1st, $actualDimensions2nd); + } + + /** + * tests that MultiDimensionProvider will throw exception when all dimension providers has nothing to return + * + * @expectedException \LogicException + * @expectedExceptionMessage Can`t multiple dimensions because some of them are empty. + */ + public function testMultiDimensionProviderWithEmptyDataProvider() + { + // collect actual dimensions + $multiDimensionProvider = new MultiDimensionProvider( + [ + $this->getDimensionProviderMock([]), + $this->getDimensionProviderMock([]), + ] + ); + + $actualDimensions = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions[] = $dimension; + } + } + + /** + * tests that MultiDimensionProvider will throw exception when one dimension providers has nothing to return + * + * @expectedException \LogicException + * @expectedExceptionMessage Can`t multiple dimensions because some of them are empty. + */ + public function testMultiDimensionProviderWithMixedDataProvider() + { + + // prepare expected dimensions + $dimensionXData = [ + $this->getDimensionMock('x', '1'), + $this->getDimensionMock('x', '2'), + $this->getDimensionMock('x', '3'), + ]; + + $dimensionYData = [ + $this->getDimensionMock('y', '1'), + $this->getDimensionMock('y', '2'), + $this->getDimensionMock('y', '3'), + $this->getDimensionMock('y', '4'), + $this->getDimensionMock('y', '5'), + ]; + + $dimensionZData = []; + + // collect actual dimensions + $multiDimensionProvider = new MultiDimensionProvider( + [ + $this->getDimensionProviderMock($dimensionXData), + $this->getDimensionProviderMock($dimensionYData), + $this->getDimensionProviderMock($dimensionZData), + ] + ); + + $actualDimensions = []; + foreach ($multiDimensionProvider as $dimension) { + $actualDimensions[] = $dimension; + } + } + + private function getDimensionProviderMock($dimensions) + { + $dimensionProviderMock = $this->getMockBuilder(DimensionProviderInterface::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->disableArgumentCloning() + ->disallowMockingUnknownTypes() + ->setMethods(['getIterator']) + ->getMockForAbstractClass(); + + $dimensionProviderMock->expects($this->any()) + ->method('getIterator') + ->will( + $this->returnCallback( + function () use ($dimensions) { + return \SplFixedArray::fromArray($dimensions); + } + ) + ); + + return $dimensionProviderMock; + } + + private function getDimensionMock(string $name, string $value) + { + $dimensionMock = $this->getMockBuilder(Dimension::class) + ->disableOriginalConstructor() + ->disableOriginalClone() + ->disableArgumentCloning() + ->disallowMockingUnknownTypes() + ->setMethods(['getName', 'getValue']) + ->getMock(); + + $dimensionMock->expects($this->any()) + ->method('getName') + ->willReturn($name); + + $dimensionMock->expects($this->any()) + ->method('getValue') + ->willReturn($value); + + return $dimensionMock; + } +} diff --git a/lib/internal/Magento/Framework/Indexer/Test/Unit/ScopeResolver/IndexScopeResolverTest.php b/lib/internal/Magento/Framework/Indexer/Test/Unit/ScopeResolver/IndexScopeResolverTest.php index ae898657ccecf..b193a3eecdd98 100644 --- a/lib/internal/Magento/Framework/Indexer/Test/Unit/ScopeResolver/IndexScopeResolverTest.php +++ b/lib/internal/Magento/Framework/Indexer/Test/Unit/ScopeResolver/IndexScopeResolverTest.php @@ -103,9 +103,19 @@ public function resolveDataProvider() ], [ 'index' => 'index_name', - 'dimensions' => [['dimension', 10], ['dimension', 20]], + 'dimensions' => [['first', 10], ['second', 20]], // actually you will get exception here thrown in ScopeResolverInterface - 'expected' => 'index_name_dimension10_dimension20' + 'expected' => 'index_name_first10_second20' + ], + [ + 'index' => 'index_name', + 'dimensions' => [['second', 10], ['first', 20]], + 'expected' => 'index_name_first20_second10' + ], + [ + 'index' => 'index_name', + 'dimensions' => [[-1, 10], ['first', 20]], + 'expected' => 'index_name_-110_first20' ] ]; } diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index 82738a30266d8..e21841b48bc13 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -268,7 +268,7 @@ public function getNext($type, $method, $code = '__self') $this->_inheritPlugins($type); } $key = $type . '_' . lcfirst($method) . '_' . $code; - return isset($this->_processed[$key]) ? $this->_processed[$key] : null; + return $this->_processed[$key] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Locale/Format.php b/lib/internal/Magento/Framework/Locale/Format.php index 00379c87daaf9..ca50cdb2440f4 100644 --- a/lib/internal/Magento/Framework/Locale/Format.php +++ b/lib/internal/Magento/Framework/Locale/Format.php @@ -65,7 +65,7 @@ public function getNumber($value) } //trim spaces and apostrophes - $value = str_replace(['\'', ' '], '', $value); + $value = preg_replace('/[^0-9^\^.,-]/m', '', $value); $separatorComa = strpos($value, ','); $separatorDot = strpos($value, '.'); @@ -131,7 +131,6 @@ public function getPriceFormat($localeCode = null, $currencyCode = null) } else { $group = strrpos($format, '.'); } - $integerRequired = strpos($format, '.') - strpos($format, '0'); $result = [ //TODO: change interface @@ -141,7 +140,7 @@ public function getPriceFormat($localeCode = null, $currencyCode = null) 'decimalSymbol' => $decimalSymbol, 'groupSymbol' => $groupSymbol, 'groupLength' => $group, - 'integerRequired' => $integerRequired, + 'integerRequired' => $totalPrecision == 0, ]; return $result; diff --git a/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php index 5ef1a3097402b..3e917425c31e0 100644 --- a/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php +++ b/lib/internal/Magento/Framework/Locale/Test/Unit/CurrencyTest.php @@ -7,7 +7,6 @@ namespace Magento\Framework\Locale\Test\Unit; use Magento\Framework\Locale\Currency; -use Magento\Framework\Locale\CurrencyInterface; class CurrencyTest extends \PHPUnit\Framework\TestCase { diff --git a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php index 41583fd1383a5..1141f451c13a5 100644 --- a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php +++ b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php @@ -33,6 +33,9 @@ class FormatTest extends \PHPUnit\Framework\TestCase */ protected $currency; + /** + * {@inheritDoc} + */ protected function setUp() { $this->currency = $this->getMockBuilder(\Magento\Directory\Model\Currency::class) @@ -41,9 +44,7 @@ protected function setUp() $this->scope = $this->getMockBuilder(\Magento\Framework\App\ScopeInterface::class) ->setMethods(['getCurrentCurrency']) ->getMockForAbstractClass(); - $this->scope->expects($this->once()) - ->method('getCurrentCurrency') - ->willReturn($this->currency); + $this->scopeResolver = $this->getMockBuilder(\Magento\Framework\App\ScopeResolverInterface::class) ->setMethods(['getScope']) ->getMockForAbstractClass(); @@ -52,7 +53,10 @@ protected function setUp() ->willReturn($this->scope); $this->localeResolver = $this->getMockBuilder(\Magento\Framework\Locale\ResolverInterface::class) ->getMock(); + + /** @var \Magento\Directory\Model\CurrencyFactory|\PHPUnit_Framework_MockObject_MockObject $currencyFactory */ $currencyFactory = $this->getMockBuilder(\Magento\Directory\Model\CurrencyFactory::class) + ->disableOriginalConstructor() ->getMock(); $this->formatModel = new \Magento\Framework\Locale\Format( @@ -63,21 +67,26 @@ protected function setUp() } /** - * @param $localeCode - * @param $expectedResult + * @param string $localeCode + * @param array $expectedResult * @dataProvider getPriceFormatDataProvider */ - public function testGetPriceFormat($localeCode, $expectedResult) + public function testGetPriceFormat($localeCode, array $expectedResult): void { + $this->scope->expects($this->once()) + ->method('getCurrentCurrency') + ->willReturn($this->currency); + $result = $this->formatModel->getPriceFormat($localeCode); $intersection = array_intersect_assoc($result, $expectedResult); $this->assertCount(count($expectedResult), $intersection); } /** + * * @return array */ - public function getPriceFormatDataProvider() + public function getPriceFormatDataProvider(): array { return [ ['en_US', ['decimalSymbol' => '.', 'groupSymbol' => ',']], @@ -86,4 +95,35 @@ public function getPriceFormatDataProvider() ['uk_UA', ['decimalSymbol' => ',', 'groupSymbol' => ' ']] ]; } + + /** + * + * @param mixed $value + * @param float $expected + * @dataProvider provideNumbers + */ + public function testGetNumber($value, $expected): void + { + $this->assertEquals($expected, $this->formatModel->getNumber($value)); + } + + /** + * + * @return array + */ + public function provideNumbers(): array + { + return [ + [' 2345.4356,1234', 23454356.1234], + ['+23,3452.123', 233452.123], + ['12343', 12343], + ['-9456km', -9456], + ['0', 0], + ['2 054,10', 2054.1], + ['2046,45', 2046.45], + ['2 054.52', 2054.52], + ['2,46 GB', 2.46], + ['2,054.00', 2054], + ]; + } } diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php index a8374be59ff65..eac5d74c6e3dc 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php @@ -11,12 +11,14 @@ use Magento\Framework\App\TemplateTypesInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Mail\MessageInterface; +use Magento\Framework\Mail\MessageInterfaceFactory; use Magento\Framework\Mail\TransportInterfaceFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; /** * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TransportBuilder { @@ -88,25 +90,35 @@ class TransportBuilder */ protected $mailTransportFactory; + /** + * @var \Magento\Framework\Mail\MessageInterfaceFactory + */ + private $messageFactory; + /** * @param FactoryInterface $templateFactory * @param MessageInterface $message * @param SenderResolverInterface $senderResolver * @param ObjectManagerInterface $objectManager * @param TransportInterfaceFactory $mailTransportFactory + * @param MessageInterfaceFactory $messageFactory + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( FactoryInterface $templateFactory, MessageInterface $message, SenderResolverInterface $senderResolver, ObjectManagerInterface $objectManager, - TransportInterfaceFactory $mailTransportFactory + TransportInterfaceFactory $mailTransportFactory, + MessageInterfaceFactory $messageFactory = null ) { $this->templateFactory = $templateFactory; - $this->message = $message; $this->objectManager = $objectManager; $this->_senderResolver = $senderResolver; $this->mailTransportFactory = $mailTransportFactory; + $this->messageFactory = $messageFactory ?: $this->objectManager->get(MessageInterfaceFactory::class); + $this->message = $this->messageFactory->create(); } /** @@ -242,7 +254,7 @@ public function getTransport() */ protected function reset() { - $this->message = $this->objectManager->create(\Magento\Framework\Mail\Message::class); + $this->message = $this->messageFactory->create(); $this->templateIdentifier = null; $this->templateVars = null; $this->templateOptions = null; diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php index e79d12310436c..731db30bada06 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php @@ -8,7 +8,11 @@ use Magento\Framework\App\TemplateTypesInterface; use Magento\Framework\Mail\MessageInterface; +use Magento\Framework\Mail\MessageInterfaceFactory; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class TransportBuilderTest extends \PHPUnit\Framework\TestCase { /** @@ -41,6 +45,11 @@ class TransportBuilderTest extends \PHPUnit\Framework\TestCase */ protected $senderResolverMock; + /** + * @var \Magento\Framework\Mail\MessageInterfaceFactory| \PHPUnit_Framework_MockObject_MockObject + */ + private $messageFactoryMock; + /** * @var \PHPUnit_Framework_MockObject_MockObject */ @@ -60,7 +69,12 @@ protected function setUp() \Magento\Framework\Mail\TransportInterfaceFactory::class )->disableOriginalConstructor() ->setMethods(['create']) - ->getMock(); + ->getMockForAbstractClass(); + $this->messageFactoryMock = $this->getMockBuilder(\Magento\Framework\Mail\MessageInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->messageFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($this->messageMock); $this->builder = $objectManagerHelper->getObject( $this->builderClassName, [ @@ -68,7 +82,8 @@ protected function setUp() 'message' => $this->messageMock, 'objectManager' => $this->objectManagerMock, 'senderResolver' => $this->senderResolverMock, - 'mailTransportFactory' => $this->mailTransportFactoryMock + 'mailTransportFactory' => $this->mailTransportFactoryMock, + 'messageFactory' => $this->messageFactoryMock ] ); } @@ -122,10 +137,7 @@ public function testGetTransport($templateType, $messageType, $bodyText, $templa ->with($this->equalTo(['message' => $this->messageMock])) ->willReturn($transport); - $this->objectManagerMock->expects($this->at(0)) - ->method('create') - ->with($this->equalTo(\Magento\Framework\Mail\Message::class)) - ->willReturn($transport); + $this->messageFactoryMock->expects($this->once())->method('create')->willReturn($transport); $this->builder->setTemplateIdentifier('identifier')->setTemplateVars($vars)->setTemplateOptions($options); $this->assertInstanceOf(\Magento\Framework\Mail\TransportInterface::class, $this->builder->getTransport()); diff --git a/lib/internal/Magento/Framework/Math/FloatComparator.php b/lib/internal/Magento/Framework/Math/FloatComparator.php new file mode 100644 index 0000000000000..4053404369956 --- /dev/null +++ b/lib/internal/Magento/Framework/Math/FloatComparator.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Math; + +/** + * Contains methods to compare float digits. + * + * @api + */ +class FloatComparator +{ + /** + * Precision for floats comparing. + * + * @var float + */ + private static $epsilon = 0.00001; + + /** + * Compares two float digits. + * + * @param float $a + * @param float $b + * @return bool + */ + public function equal(float $a, float $b): bool + { + return abs($a - $b) <= self::$epsilon; + } + + /** + * Compares if the first argument greater than the second argument. + * + * @param float $a + * @param float $b + * @return bool + */ + public function greaterThan(float $a, float $b): bool + { + return ($a - $b) > self::$epsilon; + } + + /** + * Compares if the first argument greater or equal to the second. + * + * @param float $a + * @param float $b + * @return bool + */ + public function greaterThanOrEqual(float $a, float $b): bool + { + return $this->equal($a, $b) || $this->greaterThan($a, $b); + } +} diff --git a/lib/internal/Magento/Framework/Math/Test/Unit/FloatComparatorTest.php b/lib/internal/Magento/Framework/Math/Test/Unit/FloatComparatorTest.php new file mode 100644 index 0000000000000..c9e5143ee789e --- /dev/null +++ b/lib/internal/Magento/Framework/Math/Test/Unit/FloatComparatorTest.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Math\Test\Unit; + +use Magento\Framework\Math\FloatComparator; +use PHPUnit\Framework\TestCase; + +class FloatComparatorTest extends TestCase +{ + /** + * @var FloatComparator + */ + private $comparator; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->comparator = new FloatComparator(); + } + + /** + * Checks a case when `a` and `b` are equal. + * + * @param float $a + * @param float $b + * @param bool $expected + * @dataProvider eqDataProvider + */ + public function testEq(float $a, float $b, bool $expected) + { + self::assertEquals($expected, $this->comparator->equal($a, $b)); + } + + /** + * Gets list of variations to compare equal float. + * + * @return array + */ + public function eqDataProvider(): array + { + return [ + [10, 10.00001, true], + [10, 10.000001, true], + [10.0000099, 10.00001, true], + [1, 1.0001, false], + [1, -1.00001, false], + ]; + } + + /** + * Checks a case when `a` > `b`. + * + * @param float $a + * @param float $b + * @param bool $expected + * @dataProvider gtDataProvider + */ + public function testGt(float $a, float $b, bool $expected) + { + self::assertEquals($expected, $this->comparator->greaterThan($a, $b)); + } + + /** + * Gets list of variations to compare if `a` > `b`. + * + * @return array + */ + public function gtDataProvider(): array + { + return [ + [10, 10.00001, false], + [10, 10.000001, false], + [10.0000099, 10.00001, false], + [1.0001, 1, true], + [1, -1.00001, true], + ]; + } + + /** + * Checks a case when `a` >= `b`. + * + * @param float $a + * @param float $b + * @param bool $expected + * @dataProvider gteDataProvider + */ + public function testGte(float $a, float $b, bool $expected) + { + self::assertEquals($expected, $this->comparator->greaterThanOrEqual($a, $b)); + } + + /** + * Gets list of variations to compare if `a` >= `b`. + * + * @return array + */ + public function gteDataProvider(): array + { + return [ + [10, 10.00001, true], + [10, 10.000001, true], + [10.0000099, 10.00001, true], + [1.0001, 1, true], + [1, -1.00001, true], + [1.0001, 1.001, false], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Message/Collection.php b/lib/internal/Magento/Framework/Message/Collection.php index 8805d433f4ffb..32e84fc28f5a0 100644 --- a/lib/internal/Magento/Framework/Message/Collection.php +++ b/lib/internal/Magento/Framework/Message/Collection.php @@ -136,7 +136,7 @@ public function getItems() */ public function getItemsByType($type) { - return isset($this->messages[$type]) ? $this->messages[$type] : []; + return $this->messages[$type] ?? []; } /** diff --git a/lib/internal/Magento/Framework/Message/Test/Unit/AbstractMessageTest.php b/lib/internal/Magento/Framework/Message/Test/Unit/AbstractMessageTest.php index 550f135ddc545..f4b73bdd2f293 100644 --- a/lib/internal/Magento/Framework/Message/Test/Unit/AbstractMessageTest.php +++ b/lib/internal/Magento/Framework/Message/Test/Unit/AbstractMessageTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Message\Test\Unit; -use Magento\Framework\Message\MessageInterface; - /** * \Magento\Framework\Message\AbstractMessage test case */ diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index e7ffcde03ff64..1cffba2543b0b 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -154,9 +154,7 @@ public function getCustomAttributes() public function getCustomAttribute($attributeCode) { $this->initializeCustomAttributes(); - return isset($this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode]) - ? $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] - : null; + return $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/DeleteEntityRow.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/DeleteEntityRow.php index 7ec43220dc85c..d7d63232c1191 100755 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/DeleteEntityRow.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/DeleteEntityRow.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Model\ResourceModel\Db; use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\EntityManager\EntityMetadata; class DeleteEntityRow { diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Relation/ActionPool.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Relation/ActionPool.php index 433762556693d..f3ac5ed5ee1f8 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Relation/ActionPool.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Relation/ActionPool.php @@ -7,7 +7,6 @@ namespace Magento\Framework\Model\ResourceModel\Db\Relation; use Magento\Framework\ObjectManagerInterface as ObjectManager; -use Magento\Framework\Model\ResourceModel\Db\ProcessEntityRelationInterface; /** * Class ActionPool @@ -50,9 +49,6 @@ public function getActions($entityType, $actionName) } foreach ($this->relationActions[$entityType][$actionName] as $actionClassName) { $action = $this->objectManager->get($actionClassName); - //if (!$action instanceof ProcessEntityRelationInterface) { - // throw new \Exception('Not compliant with action interface'); - //} $actions[] = $action; } return $actions; diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/AbstractExtensibleModelTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/AbstractExtensibleModelTest.php index 1b16427040709..7e68b8daf2aef 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/AbstractExtensibleModelTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/AbstractExtensibleModelTest.php @@ -7,7 +7,6 @@ namespace Magento\Framework\Model\Test\Unit; use Magento\Framework\Api\AttributeValue; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php index 147cac47d3829..4f27f083509d7 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/Collection/AbstractCollectionTest.php @@ -7,7 +7,6 @@ namespace Magento\Framework\Model\Test\Unit\ResourceModel\Db\Collection; use Magento\Framework\DB\Select; -use Magento\Framework\DataObject as MagentoObject; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\ObjectManagerInterface; diff --git a/lib/internal/Magento/Framework/Module/Dir.php b/lib/internal/Magento/Framework/Module/Dir.php index 77174bb51305d..e6b60453b9577 100644 --- a/lib/internal/Magento/Framework/Module/Dir.php +++ b/lib/internal/Magento/Framework/Module/Dir.php @@ -9,7 +9,6 @@ use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\Component\ComponentRegistrarInterface; -use Magento\Framework\Filesystem; class Dir { diff --git a/lib/internal/Magento/Framework/Module/FullModuleList.php b/lib/internal/Magento/Framework/Module/FullModuleList.php index 5ad5b05a413ef..c6e403dee0898 100644 --- a/lib/internal/Magento/Framework/Module/FullModuleList.php +++ b/lib/internal/Magento/Framework/Module/FullModuleList.php @@ -55,7 +55,7 @@ public function getAll() public function getOne($name) { $data = $this->getAll(); - return isset($data[$name]) ? $data[$name] : null; + return $data[$name] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Module/ModuleList.php b/lib/internal/Magento/Framework/Module/ModuleList.php index 406aa9efd31a0..6ee061cffb3d0 100644 --- a/lib/internal/Magento/Framework/Module/ModuleList.php +++ b/lib/internal/Magento/Framework/Module/ModuleList.php @@ -90,7 +90,7 @@ public function getAll() public function getOne($name) { $enabled = $this->getAll(); - return isset($enabled[$name]) ? $enabled[$name] : null; + return $enabled[$name] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Module/ModuleResource.php b/lib/internal/Magento/Framework/Module/ModuleResource.php index 427f8a6f8e959..b453ea4cba095 100644 --- a/lib/internal/Magento/Framework/Module/ModuleResource.php +++ b/lib/internal/Magento/Framework/Module/ModuleResource.php @@ -87,7 +87,7 @@ public function getDbVersion($moduleName) return false; } $this->_loadVersion('db'); - return isset(self::$schemaVersions[$moduleName]) ? self::$schemaVersions[$moduleName] : false; + return self::$schemaVersions[$moduleName] ?? false; } /** @@ -119,7 +119,7 @@ public function getDataVersion($moduleName) return false; } $this->_loadVersion('data'); - return isset(self::$dataVersions[$moduleName]) ? self::$dataVersions[$moduleName] : false; + return self::$dataVersions[$moduleName] ?? false; } /** diff --git a/lib/internal/Magento/Framework/Module/PackageInfo.php b/lib/internal/Magento/Framework/Module/PackageInfo.php index 7edb0c04ebf98..0dce507ba26f4 100644 --- a/lib/internal/Magento/Framework/Module/PackageInfo.php +++ b/lib/internal/Magento/Framework/Module/PackageInfo.php @@ -283,6 +283,6 @@ public function getConflict($moduleName) public function getVersion($moduleName) { $this->load(); - return isset($this->modulePackageVersionMap[$moduleName]) ? $this->modulePackageVersionMap[$moduleName] : ''; + return $this->modulePackageVersionMap[$moduleName] ?? ''; } } diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/Dir/ReaderTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/Dir/ReaderTest.php index f41f9bbd0d239..59249e3b92ae9 100644 --- a/lib/internal/Magento/Framework/Module/Test/Unit/Dir/ReaderTest.php +++ b/lib/internal/Magento/Framework/Module/Test/Unit/Dir/ReaderTest.php @@ -11,7 +11,6 @@ namespace Magento\Framework\Module\Test\Unit\Dir; use Magento\Framework\Config\FileIteratorFactory; -use Magento\Framework\Filesystem; use Magento\Framework\Module\Dir; class ReaderTest extends \PHPUnit\Framework\TestCase diff --git a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php index 12ddfea8c83d1..255f5783dbc60 100644 --- a/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php +++ b/lib/internal/Magento/Framework/Module/Test/Unit/ManagerTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Module\Test\Unit; -use Magento\Framework\Module\Plugin\DbStatusValidator; - class ManagerTest extends \PHPUnit\Framework\TestCase { /** diff --git a/lib/internal/Magento/Framework/Mview/Config/Converter.php b/lib/internal/Magento/Framework/Mview/Config/Converter.php index 55b2f1da21cb9..5c33ac150d00a 100644 --- a/lib/internal/Magento/Framework/Mview/Config/Converter.php +++ b/lib/internal/Magento/Framework/Mview/Config/Converter.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\Mview\Config; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Mview\View\SubscriptionInterface; class Converter implements \Magento\Framework\Config\ConverterInterface diff --git a/lib/internal/Magento/Framework/Mview/Config/SchemaLocator.php b/lib/internal/Magento/Framework/Mview/Config/SchemaLocator.php index 7cafc35ba1451..7eeea70ffe337 100644 --- a/lib/internal/Magento/Framework/Mview/Config/SchemaLocator.php +++ b/lib/internal/Magento/Framework/Mview/Config/SchemaLocator.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Mview\Config; -use Magento\Framework\App\Filesystem\DirectoryList; - class SchemaLocator implements \Magento\Framework\Config\SchemaLocatorInterface { /** diff --git a/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php b/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php index ea8b7cfc068ac..4830c5e140917 100644 --- a/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php +++ b/lib/internal/Magento/Framework/Mview/Test/Unit/Config/ReaderTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\Mview\Test\Unit\Config; -use Magento\Framework\App\Filesystem\DirectoryList; - class ReaderTest extends \PHPUnit\Framework\TestCase { /** diff --git a/lib/internal/Magento/Framework/Mview/View/Changelog.php b/lib/internal/Magento/Framework/Mview/View/Changelog.php index 0f441b7728fce..4fb06ce3f06fd 100644 --- a/lib/internal/Magento/Framework/Mview/View/Changelog.php +++ b/lib/internal/Magento/Framework/Mview/View/Changelog.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Mview\View; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Phrase; class Changelog implements ChangelogInterface diff --git a/lib/internal/Magento/Framework/Notification/MessageList.php b/lib/internal/Magento/Framework/Notification/MessageList.php index 8fb91890b2ff0..ac753b48c8944 100644 --- a/lib/internal/Magento/Framework/Notification/MessageList.php +++ b/lib/internal/Magento/Framework/Notification/MessageList.php @@ -72,7 +72,7 @@ protected function _loadMessages() public function getMessageByIdentity($identity) { $this->_loadMessages(); - return isset($this->_messages[$identity]) ? $this->_messages[$identity] : null; + return $this->_messages[$identity] ?? null; } /** diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php index 362cbb2e887c7..be484f074342d 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php @@ -156,9 +156,6 @@ protected function _getCollectionFactoryClassName() protected function _getPersistorClassName() { $target = $this->getSourceClassName(); -// if (substr($target, -9) == 'Interface') { -// $target = substr($target, 1, strlen($target) -9); -// } return $target . 'Persistor'; } diff --git a/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php b/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php index e4e82fc1ac7b3..492af22815311 100644 --- a/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php +++ b/lib/internal/Magento/Framework/ObjectManager/DefinitionFactory.php @@ -6,10 +6,7 @@ namespace Magento\Framework\ObjectManager; use Magento\Framework\Filesystem\DriverInterface; -use Magento\Framework\Interception\Code\Generator as InterceptionGenerator; use Magento\Framework\ObjectManager\Definition\Runtime; -use Magento\Framework\ObjectManager\Profiler\Code\Generator as ProfilerGenerator; -use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Code\Generator\Autoloader; /** diff --git a/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php b/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php index 020159985105d..15c4cb098b84d 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php +++ b/lib/internal/Magento/Framework/ObjectManager/Factory/AbstractFactory.php @@ -5,7 +5,11 @@ */ namespace Magento\Framework\ObjectManager\Factory; +use Magento\Framework\Exception\RuntimeException; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Phrase; +use Psr\Log\LoggerInterface; +use Magento\Framework\App\ObjectManager; abstract class AbstractFactory implements \Magento\Framework\ObjectManager\FactoryInterface { @@ -104,11 +108,23 @@ public function getDefinitions() * @param array $args * * @return object - * + * @throws RuntimeException */ protected function createObject($type, $args) { - return new $type(...array_values($args)); + try { + return new $type(...array_values($args)); + } catch (\TypeError $exception) { + /** @var LoggerInterface $logger */ + $logger = ObjectManager::getInstance()->get(LoggerInterface::class); + $logger->critical( + sprintf('Type Error occurred when creating object: %s, %s', $type, $exception->getMessage()) + ); + + throw new RuntimeException( + new Phrase('Type Error occurred when creating object: %type', ['type' => $type]) + ); + } } /** diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/RepositoryTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/RepositoryTest.php index 1ca755b1281ec..72eccbc5e7dea 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/RepositoryTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Code/Generator/RepositoryTest.php @@ -6,7 +6,6 @@ namespace Magento\Framework\ObjectManager\Test\Unit\Code\Generator; use Magento\Framework\Api\Test\Unit\Code\Generator\EntityChildTestAbstract; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class RepositoryTest diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/Fixture/Polymorphous.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/Fixture/Polymorphous.php index ebb7d76dcb63c..0c1a2128560f8 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/Fixture/Polymorphous.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/Fixture/Polymorphous.php @@ -26,6 +26,6 @@ public function __construct() */ public function getArg($key) { - return isset($this->args[$key]) ? $this->args[$key] : null; + return $this->args[$key] ?? null; } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Helper/CompositeTest.php b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Helper/CompositeTest.php index 14a40c76bc470..8e3681cab611f 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Helper/CompositeTest.php +++ b/lib/internal/Magento/Framework/ObjectManager/Test/Unit/Helper/CompositeTest.php @@ -9,7 +9,6 @@ use \Magento\Framework\ObjectManager\Helper\Composite; use Magento\Framework\ObjectManager\Helper\Composite as CompositeHelper; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class CompositeTest extends \PHPUnit\Framework\TestCase { diff --git a/lib/internal/Magento/Framework/Phrase/Renderer/Placeholder.php b/lib/internal/Magento/Framework/Phrase/Renderer/Placeholder.php index 4ba8c747fa12c..fd1b0c18ead17 100644 --- a/lib/internal/Magento/Framework/Phrase/Renderer/Placeholder.php +++ b/lib/internal/Magento/Framework/Phrase/Renderer/Placeholder.php @@ -40,6 +40,6 @@ public function render(array $source, array $arguments) */ private function keyToPlaceholder($key) { - return '%' . (is_int($key) ? strval($key + 1) : $key); + return '%' . (is_int($key) ? (string)($key + 1) : $key); } } diff --git a/lib/internal/Magento/Framework/Pricing/Amount/Base.php b/lib/internal/Magento/Framework/Pricing/Amount/Base.php index 2664ccc54944c..d055819a4a126 100644 --- a/lib/internal/Magento/Framework/Pricing/Amount/Base.php +++ b/lib/internal/Magento/Framework/Pricing/Amount/Base.php @@ -105,9 +105,7 @@ public function getBaseAmount() */ public function getAdjustmentAmount($adjustmentCode) { - return isset($this->adjustmentAmounts[$adjustmentCode]) - ? $this->adjustmentAmounts[$adjustmentCode] - : false; + return $this->adjustmentAmounts[$adjustmentCode] ?? false; } /** diff --git a/lib/internal/Magento/Framework/Pricing/Price/Pool.php b/lib/internal/Magento/Framework/Pricing/Price/Pool.php index b460113fc32c8..dfdd0c52681e1 100644 --- a/lib/internal/Magento/Framework/Pricing/Price/Pool.php +++ b/lib/internal/Magento/Framework/Pricing/Price/Pool.php @@ -141,6 +141,6 @@ public function offsetUnset($offset) */ public function offsetGet($offset) { - return isset($this->prices[$offset]) ? $this->prices[$offset] : null; + return $this->prices[$offset] ?? null; } } diff --git a/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php index 4fd3a17fb6c6a..18308724c0226 100644 --- a/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/CustomAttributesProcessor.php @@ -6,11 +6,8 @@ namespace Magento\Framework\Reflection; -use Magento\Framework\Phrase; use Magento\Framework\Api\AttributeInterface; use Magento\Framework\Api\AttributeValue; -use Magento\Framework\Api\SimpleDataObjectConverter; -use Zend\Code\Reflection\MethodReflection; use Magento\Framework\Api\CustomAttributesDataInterface; use Magento\Framework\Api\AttributeTypeResolverInterface; diff --git a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php index 3e9eef4fd7b15..aa978e7f337cc 100644 --- a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php @@ -11,7 +11,6 @@ use Magento\Framework\AuthorizationInterface; use Magento\Framework\Phrase; use Magento\Framework\Api\ExtensionAttributesInterface; -use Magento\Framework\Reflection\MethodsMap; use Zend\Code\Reflection\MethodReflection; /** diff --git a/lib/internal/Magento/Framework/Reflection/FieldNamer.php b/lib/internal/Magento/Framework/Reflection/FieldNamer.php index 8e4d077c52e08..a208764352f58 100644 --- a/lib/internal/Magento/Framework/Reflection/FieldNamer.php +++ b/lib/internal/Magento/Framework/Reflection/FieldNamer.php @@ -6,12 +6,7 @@ namespace Magento\Framework\Reflection; -use Magento\Framework\Phrase; -use Magento\Framework\Api\AttributeValue; -use Magento\Framework\Api\CustomAttributesDataInterface; use Magento\Framework\Api\SimpleDataObjectConverter; -use Zend\Code\Reflection\ClassReflection; -use Zend\Code\Reflection\MethodReflection; /** * Determines the name to use for fields in a data output array given method metadata. diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php index a467c4b7b5aad..e4c0294c0cfb5 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php @@ -7,7 +7,6 @@ namespace Magento\Framework\Reflection\Test\Unit; use Zend\Code\Reflection\ClassReflection; -use Magento\Framework\Exception\SerializationException; /** * NameFinder Unit Test diff --git a/lib/internal/Magento/Framework/RequireJs/Config.php b/lib/internal/Magento/Framework/RequireJs/Config.php index 08131b2cccea3..ae45e29f38911 100644 --- a/lib/internal/Magento/Framework/RequireJs/Config.php +++ b/lib/internal/Magento/Framework/RequireJs/Config.php @@ -157,7 +157,7 @@ public function getConfig() $customConfigFiles = $this->fileSource->getFiles($this->design->getDesignTheme(), self::CONFIG_FILE_NAME); foreach ($customConfigFiles as $file) { /** @var $fileReader \Magento\Framework\Filesystem\File\Read */ - $fileReader = $this->readFactory->create($file->getFileName(), DriverPool::FILE); + $fileReader = $this->readFactory->create($file->getFilename(), DriverPool::FILE); $config = $fileReader->readAll($file->getName()); $distributedConfig .= str_replace( ['%config%', '%context%'], diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php index 098782b4e814e..11bd746eb71e5 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php @@ -7,7 +7,6 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Ddl\Table; -use Magento\Framework\DB\Select; use Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder as AggregationBuilder; use Magento\Framework\Search\AdapterInterface; use Magento\Framework\Search\RequestInterface; @@ -70,7 +69,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc * @throws \LogicException */ public function query(RequestInterface $request) diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Term.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Term.php index ed3018756cdd6..78ff7b04fd39c 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Term.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Term.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder; use Magento\Framework\DB\Ddl\Table; -use Magento\Framework\DB\Select; use Magento\Framework\Search\Adapter\Mysql\Aggregation\DataProviderInterface; use Magento\Framework\Search\Request\BucketInterface as RequestBucketInterface; diff --git a/lib/internal/Magento/Framework/Search/Request/IndexScopeResolverInterface.php b/lib/internal/Magento/Framework/Search/Request/IndexScopeResolverInterface.php index d9412cb8517fa..9e907883bb2b9 100644 --- a/lib/internal/Magento/Framework/Search/Request/IndexScopeResolverInterface.php +++ b/lib/internal/Magento/Framework/Search/Request/IndexScopeResolverInterface.php @@ -6,8 +6,8 @@ namespace Magento\Framework\Search\Request; /** - * Interface \Magento\Framework\Search\Request\IndexScopeResolverInterface - * + * Resolve table name by provided dimensions. Scope Resolver must accept all dimensions that potentially can be used to + * resolve table name, but certain implementation can filter them if needed */ interface IndexScopeResolverInterface { diff --git a/lib/internal/Magento/Framework/Search/Response/Aggregation.php b/lib/internal/Magento/Framework/Search/Response/Aggregation.php index 9cb7a364ff21c..ea72597c53034 100644 --- a/lib/internal/Magento/Framework/Search/Response/Aggregation.php +++ b/lib/internal/Magento/Framework/Search/Response/Aggregation.php @@ -47,7 +47,7 @@ public function getIterator() */ public function getBucket($bucketName) { - return isset($this->buckets[$bucketName]) ? $this->buckets[$bucketName] : null; + return $this->buckets[$bucketName] ?? null; } /** diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php index ef4a4d62322ed..55d26493ca379 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Search\Test\Unit\Adapter\Mysql; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; diff --git a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php index 1f8e7cf9d0bc3..aac25400cec85 100644 --- a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php +++ b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/SerializeTest.php @@ -7,7 +7,6 @@ use Magento\Framework\Serialize\Serializer\Serialize; use Magento\Framework\Serialize\Signer; -use Psr\Log\LoggerInterface; use Magento\Framework\Serialize\InvalidSignatureException; class SerializeTest extends \PHPUnit\Framework\TestCase diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php b/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php index 6f5937e08455d..cf3449a8c3fcf 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/DbTable.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Session\SaveHandler; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Exception\SessionException; use Magento\Framework\Phrase; diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php index 53af1847d1012..cd1cef5da6ddd 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php @@ -14,13 +14,28 @@ use Magento\Framework\Filesystem; use Magento\Framework\App\Filesystem\DirectoryList; -class Redis extends \Cm\RedisSession\Handler +class Redis implements \SessionHandlerInterface { + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @var Filesystem */ private $filesystem; + /** + * @var \Cm\RedisSession\Handler[] + */ + private $connection; + /** * @param ConfigInterface $config * @param LoggerInterface $logger @@ -29,23 +44,117 @@ class Redis extends \Cm\RedisSession\Handler */ public function __construct(ConfigInterface $config, LoggerInterface $logger, Filesystem $filesystem) { + $this->config = $config; + $this->logger = $logger; $this->filesystem = $filesystem; - try { - parent::__construct($config, $logger); - } catch (ConnectionFailedException $e) { - throw new SessionException(new Phrase($e->getMessage())); + } + + /** + * Get connection + * + * @return \Cm\RedisSession\Handler + * @throws SessionException + */ + private function getConnection() + { + $pid = getmypid(); + if (!isset($this->connection[$pid])) { + try { + $this->connection[$pid] = new \Cm\RedisSession\Handler($this->config, $this->logger); + } catch (ConnectionFailedException $e) { + throw new SessionException(new Phrase($e->getMessage())); + } } + return $this->connection[$pid]; } /** - * {@inheritdoc} + * Open session + * + * @param string $savePath ignored + * @param string $sessionName ignored + * @return bool + * @throws SessionException + */ + public function open($savePath, $sessionName) + { + return $this->getConnection()->open($savePath, $sessionName); + } + + /** + * Fetch session data + * + * @param string $sessionId + * @return string + * @throws ConcurrentConnectionsExceededException + * @throws SessionException */ public function read($sessionId) { try { - return parent::read($sessionId); + return $this->getConnection()->read($sessionId); } catch (ConcurrentConnectionsExceededException $e) { require $this->filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php'); } } + + /** + * Update session + * + * @param string $sessionId + * @param string $sessionData + * @return boolean + * @throws SessionException + */ + public function write($sessionId, $sessionData) + { + return $this->getConnection()->write($sessionId, $sessionData); + } + + /** + * Destroy session + * + * @param string $sessionId + * @return boolean + * @throws SessionException + */ + public function destroy($sessionId) + { + return $this->getConnection()->destroy($sessionId); + } + + /** + * Overridden to prevent calling getLifeTime at shutdown + * + * @return bool + * @throws SessionException + */ + public function close() + { + return $this->getConnection()->close(); + } + + /** + * Garbage collection + * + * @param int $maxLifeTime ignored + * @return boolean + * @throws SessionException + * @SuppressWarnings(PHPMD.ShortMethodName) + */ + public function gc($maxLifeTime) + { + return $this->getConnection()->gc($maxLifeTime); + } + + /** + * Get the number of failed lock attempts + * + * @return int + * @throws SessionException + */ + public function getFailedLockAttempts() + { + return $this->getConnection()->getFailedLockAttempts(); + } } diff --git a/lib/internal/Magento/Framework/Session/SessionManager.php b/lib/internal/Magento/Framework/Session/SessionManager.php index e4130147a5da0..662173ad4a09a 100644 --- a/lib/internal/Magento/Framework/Session/SessionManager.php +++ b/lib/internal/Magento/Framework/Session/SessionManager.php @@ -473,7 +473,7 @@ protected function _addHost() */ protected function _getHosts() { - return isset($_SESSION[self::HOST_KEY]) ? $_SESSION[self::HOST_KEY] : []; + return $_SESSION[self::HOST_KEY] ?? []; } /** diff --git a/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/LoggerTest.php b/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/LoggerTest.php index 4594a471e008d..a604178bc36cc 100644 --- a/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/LoggerTest.php +++ b/lib/internal/Magento/Framework/Session/Test/Unit/SaveHandler/Redis/LoggerTest.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Session\Test\Unit\SaveHandler\Redis; use Cm\RedisSession\Handler\LoggerInterface; -use Magento\Framework\Session\SaveHandler\Redis\Logger; class LoggerTest extends \PHPUnit\Framework\TestCase { diff --git a/lib/internal/Magento/Framework/Setup/JsonPersistor.php b/lib/internal/Magento/Framework/Setup/JsonPersistor.php index 4094f9e0d090d..b479ffe20e460 100644 --- a/lib/internal/Magento/Framework/Setup/JsonPersistor.php +++ b/lib/internal/Magento/Framework/Setup/JsonPersistor.php @@ -19,6 +19,6 @@ class JsonPersistor */ public function persist(array $data, $path) { - return file_put_contents($path, json_encode($data)); + return file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT)); } } diff --git a/app/code/Magento/Framework/Test/Unit/Setup/SchemaListenerTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php similarity index 91% rename from app/code/Magento/Framework/Test/Unit/Setup/SchemaListenerTest.php rename to lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php index 4a02cc3e0df55..4e34b3aebbf3e 100644 --- a/app/code/Magento/Framework/Test/Unit/Setup/SchemaListenerTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php @@ -3,8 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Magento\Framework\Test\Unit\Setup; +namespace Magento\Framework\Setup\Test\Unit; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\SchemaListenerDefinition\BooleanDefinition; @@ -16,7 +17,7 @@ /** * Unit test for schema listener. * - * @package Magento\Framework\Test\Unit\Setup + * @package Magento\Framework\Setup\Test\Unit */ class SchemaListenerTest extends \PHPUnit\Framework\TestCase { @@ -30,7 +31,7 @@ class SchemaListenerTest extends \PHPUnit\Framework\TestCase */ private $objectManagerHelper; - protected function setUp() + protected function setUp() : void { $this->objectManagerHelper = new ObjectManagerHelper($this); $this->model = $this->objectManagerHelper->getObject( @@ -51,7 +52,7 @@ protected function setUp() /** * @return Table */ - private function getCreateTableDDL($tableName) + private function getCreateTableDDL($tableName) : Table { $table = new Table(); $table->setName($tableName); @@ -91,7 +92,7 @@ private function getCreateTableDDL($tableName) ); } - public function testRenameTable() + public function testRenameTable() : void { $this->model->setModuleName('First_Module'); $this->model->createTable($this->getCreateTableDDL('old_table')); @@ -101,7 +102,7 @@ public function testRenameTable() self::assertArrayNotHasKey('old_table', $tables['First_Module']); } - public function testDropIndex() + public function testDropIndex() : void { $this->model->setModuleName('First_Module'); $this->model->createTable($this->getCreateTableDDL('index_table')); @@ -109,7 +110,7 @@ public function testDropIndex() self::assertTrue($this->model->getTables()['First_Module']['index_table']['indexes']['INDEX_KEY']['disabled']); } - public function testCreateTable() + public function testCreateTable() : void { $this->model->setModuleName('First_Module'); $this->model->createTable($this->getCreateTableDDL('new_table')); @@ -125,7 +126,7 @@ public function testCreateTable() 'nullable' => false, 'default' => 'CURRENT_TIMESTAMP', 'disabled' => false, - 'onCreate' => NULL, + 'onCreate' => null, ], 'integer' => [ @@ -135,9 +136,9 @@ public function testCreateTable() 'unsigned' => false, 'nullable' => false, 'identity' => true, - 'default' => NULL, + 'default' => null, 'disabled' => false, - 'onCreate' => NULL, + 'onCreate' => null, ], 'decimal' => [ @@ -147,9 +148,9 @@ public function testCreateTable() 'precision' => '25', 'unsigned' => false, 'nullable' => false, - 'default' => NULL, + 'default' => null, 'disabled' => false, - 'onCreate' => NULL, + 'onCreate' => null, ], ], $tables['First_Module']['new_table']['columns'] @@ -201,7 +202,7 @@ public function testCreateTable() ); } - public function testDropTable() + public function testDropTable() : void { $this->model->setModuleName('Old_Module'); $this->model->createTable($this->getCreateTableDDL('old_table')); @@ -210,7 +211,7 @@ public function testDropTable() self::assertTrue($this->model->getTables()['New_Module']['old_table']['disabled']); } - public function testDropTableInSameModule() + public function testDropTableInSameModule() : void { $this->model->setModuleName('Old_Module'); $this->model->createTable($this->getCreateTableDDL('old_table')); diff --git a/app/code/Magento/Framework/Test/Unit/Setup/SchemaPersistorTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php similarity index 94% rename from app/code/Magento/Framework/Test/Unit/Setup/SchemaPersistorTest.php rename to lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php index 56f04f4c7ba77..cc88af15a262b 100644 --- a/app/code/Magento/Framework/Test/Unit/Setup/SchemaPersistorTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php @@ -3,8 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Magento\Framework\Test\Unit\Setup; +namespace Magento\Framework\Setup\Test\Unit; use Magento\Framework\Component\ComponentRegistrar; use Magento\Framework\Setup\SchemaListener; @@ -14,7 +15,7 @@ /** * Unit test for schema persistor. * - * @package Magento\Framework\Test\Unit\Setup + * @package Magento\Framework\Setup\Test\Unit */ class SchemaPersistorTest extends \PHPUnit\Framework\TestCase { @@ -38,7 +39,7 @@ class SchemaPersistorTest extends \PHPUnit\Framework\TestCase */ private $xmlPersistor; - protected function setUp() + protected function setUp() : void { $this->componentRegistrarMock = $this->getMockBuilder(ComponentRegistrar::class) ->disableOriginalConstructor() @@ -60,7 +61,7 @@ protected function setUp() * @param array $tables * @param string $expectedXML */ - public function testPersist(array $tables, $expectedXML) + public function testPersist(array $tables, $expectedXML) : void { $moduleName = 'First_Module'; /** @var SchemaListener|\PHPUnit_Framework_MockObject_MockObject $schemaListenerMock */ @@ -88,7 +89,7 @@ public function testPersist(array $tables, $expectedXML) * * @return array */ - public function schemaListenerTablesDataProvider() + public function schemaListenerTablesDataProvider() : array { return [ [ @@ -143,6 +144,7 @@ public function schemaListenerTablesDataProvider() ] ] ], + // @codingStandardsIgnoreStart 'XMLResult' => '<?xml version="1.0"?> <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> @@ -161,6 +163,7 @@ public function schemaListenerTablesDataProvider() </index> </table> </schema>' + // @codingStandardsIgnoreEnd ] ]; } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/Cookie/_files/setcookie_mock.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/Cookie/_files/setcookie_mock.php index e97dfab795c59..f89144f9753db 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/Cookie/_files/setcookie_mock.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/Cookie/_files/setcookie_mock.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Stdlib\Cookie; -use \Magento\Framework\Stdlib\Cookie\PhpCookieManager; use \Magento\Framework\Stdlib\Test\Unit\Cookie\PhpCookieManagerTest; /** diff --git a/lib/internal/Magento/Framework/System/Dirs.php b/lib/internal/Magento/Framework/System/Dirs.php index 1bab4c90b3bec..6328921d40bb0 100644 --- a/lib/internal/Magento/Framework/System/Dirs.php +++ b/lib/internal/Magento/Framework/System/Dirs.php @@ -5,8 +5,6 @@ */ namespace Magento\Framework\System; -use Magento\Framework\Filesystem\DriverInterface; - class Dirs { /** diff --git a/lib/internal/Magento/Framework/System/Ftp.php b/lib/internal/Magento/Framework/System/Ftp.php index 216026be8d72e..8bf898965cbc3 100644 --- a/lib/internal/Magento/Framework/System/Ftp.php +++ b/lib/internal/Magento/Framework/System/Ftp.php @@ -6,8 +6,6 @@ namespace Magento\Framework\System; -use Magento\Framework\Filesystem\DriverInterface; - /** * Class to work with remote FTP server */ @@ -107,6 +105,12 @@ public function validateConnectionString($string) if ($data['scheme'] != 'ftp') { throw new \Exception("Support for scheme unsupported: '{$data['scheme']}'"); } + + // Decode user & password strings from URL + foreach (array_intersect(array_keys($data), ['user','pass']) as $key) { + $data[$key] = urldecode($data[$key]); + } + return $data; } diff --git a/lib/internal/Magento/Framework/Test/Unit/TranslateTest.php b/lib/internal/Magento/Framework/Test/Unit/TranslateTest.php deleted file mode 100644 index cbf3f89717bc6..0000000000000 --- a/lib/internal/Magento/Framework/Test/Unit/TranslateTest.php +++ /dev/null @@ -1,356 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Test\Unit; - -use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\Translate; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class TranslateTest extends \PHPUnit\Framework\TestCase -{ - /** @var Translate */ - protected $translate; - - /** @var \Magento\Framework\View\DesignInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $viewDesign; - - /** @var \Magento\Framework\Cache\FrontendInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $cache; - - /** @var \Magento\Framework\View\FileSystem|\PHPUnit_Framework_MockObject_MockObject */ - protected $viewFileSystem; - - /** @var \Magento\Framework\Module\ModuleList|\PHPUnit_Framework_MockObject_MockObject */ - protected $moduleList; - - /** @var \Magento\Framework\Module\Dir\Reader|\PHPUnit_Framework_MockObject_MockObject */ - protected $modulesReader; - - /** @var \Magento\Framework\App\ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeResolver; - - /** @var \Magento\Framework\Translate\ResourceInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $resource; - - /** @var \Magento\Framework\Locale\ResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $locale; - - /** @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject */ - protected $appState; - - /** @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject */ - protected $filesystem; - - /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $request; - - /** @var \Magento\Framework\File\Csv|\PHPUnit_Framework_MockObject_MockObject */ - protected $csvParser; - - /** @var \Magento\Framework\App\Language\Dictionary|\PHPUnit_Framework_MockObject_MockObject */ - protected $packDictionary; - - /** @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $directory; - - protected function setUp() - { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->viewDesign = $this->createMock(\Magento\Framework\View\DesignInterface::class); - $this->cache = $this->createMock(\Magento\Framework\Cache\FrontendInterface::class); - $this->viewFileSystem = $this->createMock(\Magento\Framework\View\FileSystem::class); - $this->moduleList = $this->createMock(\Magento\Framework\Module\ModuleList::class); - $this->modulesReader = $this->createMock(\Magento\Framework\Module\Dir\Reader::class); - $this->scopeResolver = $this->createMock(\Magento\Framework\App\ScopeResolverInterface::class); - $this->resource = $this->createMock(\Magento\Framework\Translate\ResourceInterface::class); - $this->locale = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); - $this->appState = $this->createMock(\Magento\Framework\App\State::class); - $this->request = $this->getMockForAbstractClass( - \Magento\Framework\App\RequestInterface::class, - [], - '', - false, - false, - true, - ['getParam', 'getControllerModule'] - ); - $this->csvParser = $this->createMock(\Magento\Framework\File\Csv::class); - $this->packDictionary = $this->createMock(\Magento\Framework\App\Language\Dictionary::class); - $this->directory = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $filesystem = $this->createMock(\Magento\Framework\Filesystem::class); - $filesystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); - - $this->translate = new Translate( - $this->viewDesign, - $this->cache, - $this->viewFileSystem, - $this->moduleList, - $this->modulesReader, - $this->scopeResolver, - $this->resource, - $this->locale, - $this->appState, - $filesystem, - $this->request, - $this->csvParser, - $this->packDictionary - ); - - $serializerMock = $this->createMock(SerializerInterface::class); - $serializerMock->method('serialize') - ->willReturnCallback(function ($data) { - return json_encode($data); - }); - $serializerMock->method('unserialize') - ->willReturnCallback(function ($string) { - return json_decode($string, true); - }); - $objectManager->setBackwardCompatibleProperty( - $this->translate, - 'serializer', - $serializerMock - ); - } - - /** - * @param string $area - * @param bool $forceReload - * @param array $cachedData - * @return void - * @dataProvider dataProviderLoadDataCachedTranslation - */ - public function testLoadDataCachedTranslation($area, $forceReload, array $cachedData) - { - $this->expectsSetConfig('Magento/luma'); - - $this->cache->expects($this->once()) - ->method('load') - ->willReturn(json_encode($cachedData)); - - $this->appState->expects($this->exactly($area ? 0 : 1)) - ->method('getAreaCode') - ->willReturn('frontend'); - - $this->translate->loadData($area, $forceReload); - $this->assertEquals($cachedData, $this->translate->getData()); - } - - /** - * @return array - */ - public function dataProviderLoadDataCachedTranslation() - { - $cachedData = ['cached 1' => 'translated 1', 'cached 2' => 'translated 2']; - return [ - ['adminhtml', false, $cachedData], - ['frontend', false, $cachedData], - [null, false, $cachedData], - ]; - } - - /** - * @param string $area - * @param bool $forceReload - * @return void - * @dataProvider dataProviderForTestLoadData - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function testLoadData($area, $forceReload) - { - $this->expectsSetConfig('Magento/luma'); - - $this->appState->expects($this->exactly($area ? 0 : 1)) - ->method('getAreaCode') - ->willReturn('frontend'); - - $this->cache->expects($this->exactly($forceReload ? 0 : 1)) - ->method('load') - ->willReturn(false); - - $this->directory->expects($this->any())->method('isExist')->willReturn(true); - - // _loadModuleTranslation() - $modules = ['some_module', 'other_module', 'another_module', 'current_module']; - $this->request->expects($this->any()) - ->method('getControllerModule') - ->willReturn('current_module'); - $this->moduleList->expects($this->once())->method('getNames')->willReturn($modules); - $moduleData = [ - 'module original' => 'module translated', - 'module theme' => 'module-theme original translated', - 'module pack' => 'module-pack original translated', - 'module db' => 'module-db original translated', - ]; - $this->modulesReader->expects($this->any())->method('getModuleDir')->willReturn('/app/module'); - $themeData = [ - 'theme original' => 'theme translated', - 'module theme' => 'theme translated overwrite', - 'module pack' => 'theme-pack translated overwrite', - 'module db' => 'theme-db translated overwrite', - ]; - $this->csvParser->expects($this->any()) - ->method('getDataPairs') - ->willReturnMap( - [ - ['/app/module/en_US.csv', 0, 1, $moduleData], - ['/app/module/en_GB.csv', 0, 1, $moduleData], - ['/theme.csv', 0, 1, $themeData], - ] - ); - - // _loadPackTranslation - $packData = [ - 'pack original' => 'pack translated', - 'module pack' => 'pack translated overwrite', - 'module db' => 'pack-db translated overwrite', - ]; - $this->packDictionary->expects($this->once())->method('getDictionary')->willReturn($packData); - - // _loadThemeTranslation() - $this->viewFileSystem->expects($this->any()) - ->method('getLocaleFileName') - ->will($this->returnValue('/theme.csv')); - - // _loadDbTranslation() - $dbData = [ - 'db original' => 'db translated', - 'module db' => 'db translated overwrite', - ]; - $this->resource->expects($this->any())->method('getTranslationArray')->willReturn($dbData); - - $this->cache->expects($this->exactly($forceReload ? 0 : 1))->method('save'); - - $this->translate->loadData($area, $forceReload); - - $expected = [ - 'module original' => 'module translated', - 'module theme' => 'theme translated overwrite', - 'module pack' => 'theme-pack translated overwrite', - 'module db' => 'db translated overwrite', - 'theme original' => 'theme translated', - 'pack original' => 'pack translated', - 'db original' => 'db translated', - ]; - $this->assertEquals($expected, $this->translate->getData()); - } - - /** - * @return array - */ - public function dataProviderForTestLoadData() - { - return [ - ['adminhtml', true], - ['adminhtml', false], - ['frontend', true], - ['frontend', false], - [null, true], - [null, false], - ]; - } - - /** - * @param $data - * @param $result - * @return void - * @dataProvider dataProviderForTestGetData - */ - public function testGetData($data, $result) - { - $this->cache->expects($this->once()) - ->method('load') - ->will($this->returnValue(json_encode($data))); - $this->expectsSetConfig('themeId'); - $this->translate->loadData('frontend'); - $this->assertEquals($result, $this->translate->getData()); - } - - /** - * @return array - */ - public function dataProviderForTestGetData() - { - $data = ['original 1' => 'translated 1', 'original 2' => 'translated 2']; - return [ - [$data, $data], - [null, []], - ]; - } - - public function testGetLocale() - { - $this->locale->expects($this->once())->method('getLocale')->will($this->returnValue('en_US')); - $this->assertEquals('en_US', $this->translate->getLocale()); - - $this->locale->expects($this->never())->method('getLocale'); - $this->assertEquals('en_US', $this->translate->getLocale()); - - $this->locale->expects($this->never())->method('getLocale'); - $this->translate->setLocale('en_GB'); - $this->assertEquals('en_GB', $this->translate->getLocale()); - } - - public function testSetLocale() - { - $this->translate->setLocale('en_GB'); - $this->locale->expects($this->never())->method('getLocale'); - $this->assertEquals('en_GB', $this->translate->getLocale()); - } - - public function testGetTheme() - { - $this->request->expects($this->at(0))->method('getParam')->with('theme')->will($this->returnValue('')); - - $requestTheme = ['theme_title' => 'Theme Title']; - $this->request->expects($this->at(1))->method('getParam')->with('theme') - ->will($this->returnValue($requestTheme)); - - $this->assertEquals('theme', $this->translate->getTheme()); - $this->assertEquals('themeTheme Title', $this->translate->getTheme()); - } - - public function testLoadDataNoTheme() - { - $forceReload = true; - $this->expectsSetConfig(null, null); - $this->moduleList->expects($this->once())->method('getNames')->will($this->returnValue([])); - $this->appState->expects($this->once())->method('getAreaCode')->will($this->returnValue('frontend')); - $this->packDictionary->expects($this->once())->method('getDictionary')->will($this->returnValue([])); - $this->resource->expects($this->any())->method('getTranslationArray')->will($this->returnValue([])); - $this->assertEquals($this->translate, $this->translate->loadData(null, $forceReload)); - } - - /** - * Declare calls expectation for setConfig() method - */ - protected function expectsSetConfig($themeId, $localeCode = 'en_US') - { - $this->locale->expects($this->any())->method('getLocale')->will($this->returnValue($localeCode)); - $scope = new \Magento\Framework\DataObject(['code' => 'frontendCode', 'id' => 1]); - $scopeAdmin = new \Magento\Framework\DataObject(['code' => 'adminCode', 'id' => 0]); - $this->scopeResolver->expects($this->any()) - ->method('getScope') - ->will( - $this->returnValueMap( - [ - [null, $scope], - ['admin', $scopeAdmin], - ] - ) - ); - $designTheme = $this->getMockBuilder(\Magento\Theme\Model\Theme::class) - ->disableOriginalConstructor() - ->getMock(); - - $designTheme->expects($this->once()) - ->method('getThemePath') - ->willReturn($themeId); - - $this->viewDesign->expects($this->any())->method('getDesignTheme')->will($this->returnValue($designTheme)); - } -} diff --git a/lib/internal/Magento/Framework/Translate.php b/lib/internal/Magento/Framework/Translate.php index 2f80ab2befbf8..ffa8e25031064 100644 --- a/lib/internal/Magento/Framework/Translate.php +++ b/lib/internal/Magento/Framework/Translate.php @@ -7,6 +7,9 @@ namespace Magento\Framework; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\Filesystem\DriverInterface; /** * Translate library @@ -120,6 +123,11 @@ class Translate implements \Magento\Framework\TranslateInterface */ private $serializer; + /** + * @var DriverInterface + */ + private $fileDriver; + /** * @param \Magento\Framework\View\DesignInterface $viewDesign * @param \Magento\Framework\Cache\FrontendInterface $cache @@ -134,6 +142,7 @@ class Translate implements \Magento\Framework\TranslateInterface * @param \Magento\Framework\App\RequestInterface $request * @param \Magento\Framework\File\Csv $csvParser * @param \Magento\Framework\App\Language\Dictionary $packDictionary + * @param DriverInterface|null $fileDriver * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -150,7 +159,8 @@ public function __construct( \Magento\Framework\Filesystem $filesystem, \Magento\Framework\App\RequestInterface $request, \Magento\Framework\File\Csv $csvParser, - \Magento\Framework\App\Language\Dictionary $packDictionary + \Magento\Framework\App\Language\Dictionary $packDictionary, + DriverInterface $fileDriver = null ) { $this->_viewDesign = $viewDesign; $this->_cache = $cache; @@ -165,6 +175,8 @@ public function __construct( $this->directory = $filesystem->getDirectoryRead(DirectoryList::ROOT); $this->_csvParser = $csvParser; $this->packDictionary = $packDictionary; + $this->fileDriver = $fileDriver + ?? ObjectManager::getInstance()->get(File::class); $this->_config = [ self::CONFIG_AREA_KEY => null, @@ -400,7 +412,7 @@ protected function _getThemeTranslationFile($locale) protected function _getFileData($file) { $data = []; - if ($this->directory->isExist($this->directory->getRelativePath($file))) { + if ($this->fileDriver->isExists($file)) { $this->_csvParser->setDelimiter(','); $data = $this->_csvParser->getDataPairs($file); } diff --git a/lib/internal/Magento/Framework/Url.php b/lib/internal/Magento/Framework/Url.php index 11f828370d441..11aeb1c0c79b8 100644 --- a/lib/internal/Magento/Framework/Url.php +++ b/lib/internal/Magento/Framework/Url.php @@ -1074,7 +1074,7 @@ function ($match) { if ($match[1] == '?') { return isset($match[3]) ? '?' : ''; } elseif ($match[1] == '&' || $match[1] == '&') { - return isset($match[3]) ? $match[3] : ''; + return $match[3] ?? ''; } } }, diff --git a/lib/internal/Magento/Framework/Validator/Exception.php b/lib/internal/Magento/Framework/Validator/Exception.php index c70ecfabb52af..370f66c424b01 100644 --- a/lib/internal/Magento/Framework/Validator/Exception.php +++ b/lib/internal/Magento/Framework/Validator/Exception.php @@ -84,6 +84,6 @@ public function getMessages($type = '') } return $allMessages; } - return isset($this->messages[$type]) ? $this->messages[$type] : []; + return $this->messages[$type] ?? []; } } diff --git a/lib/internal/Magento/Framework/View/Asset/Merged.php b/lib/internal/Magento/Framework/View/Asset/Merged.php index 5b206b235eb11..302eb1226b8ef 100644 --- a/lib/internal/Magento/Framework/View/Asset/Merged.php +++ b/lib/internal/Magento/Framework/View/Asset/Merged.php @@ -5,6 +5,8 @@ */ namespace Magento\Framework\View\Asset; +use Magento\Framework\App\ObjectManager; + /** * \Iterator that aggregates one or more assets and provides a single public file with equivalent behavior */ @@ -40,27 +42,39 @@ class Merged implements \Iterator */ protected $contentType; + /** + * @var \Magento\Framework\App\View\Deployment\Version\StorageInterface + */ + private $versionStorage; + /** * @var bool */ protected $isInitialized = false; /** + * Merged constructor. + * * @param \Psr\Log\LoggerInterface $logger * @param MergeStrategyInterface $mergeStrategy * @param \Magento\Framework\View\Asset\Repository $assetRepo * @param MergeableInterface[] $assets + * @param \Magento\Framework\App\View\Deployment\Version\StorageInterface $versionStorage * @throws \InvalidArgumentException */ public function __construct( \Psr\Log\LoggerInterface $logger, MergeStrategyInterface $mergeStrategy, \Magento\Framework\View\Asset\Repository $assetRepo, - array $assets + array $assets, + \Magento\Framework\App\View\Deployment\Version\StorageInterface $versionStorage = null ) { $this->logger = $logger; $this->mergeStrategy = $mergeStrategy; $this->assetRepo = $assetRepo; + $this->versionStorage = $versionStorage ?: ObjectManager::getInstance()->get( + \Magento\Framework\App\View\Deployment\Version\StorageInterface::class + ); if (!$assets) { throw new \InvalidArgumentException('At least one asset has to be passed for merging.'); @@ -116,6 +130,12 @@ private function createMergedAsset(array $assets) $paths[] = $asset->getPath(); } $paths = array_unique($paths); + + $version = $this->versionStorage->load(); + if ($version) { + $paths[] = $version; + } + $filePath = md5(implode('|', $paths)) . '.' . $this->contentType; return $this->assetRepo->createArbitrary($filePath, self::getRelativeDir()); } diff --git a/lib/internal/Magento/Framework/View/Asset/Minification.php b/lib/internal/Magento/Framework/View/Asset/Minification.php index 33c82a1810db6..596add349dbfa 100644 --- a/lib/internal/Magento/Framework/View/Asset/Minification.php +++ b/lib/internal/Magento/Framework/View/Asset/Minification.php @@ -162,7 +162,16 @@ public function getExcludes($contentType) private function getMinificationExcludeValues($key) { $configValues = $this->scopeConfig->getValue($key, $this->scope) ?? []; - + //value used to be a string separated by 'newline' separator so we need to convert it to array + if (!is_array($configValues)) { + $configValuesFromString = []; + foreach (explode("\n", $configValues) as $exclude) { + if (trim($exclude) != '') { + $configValuesFromString[] = trim($exclude); + } + } + $configValues = $configValuesFromString; + } return array_values($configValues); } } diff --git a/lib/internal/Magento/Framework/View/Asset/PreProcessor/AlternativeSource.php b/lib/internal/Magento/Framework/View/Asset/PreProcessor/AlternativeSource.php index 38f2b220dee6f..7016bbdb08ab2 100644 --- a/lib/internal/Magento/Framework/View/Asset/PreProcessor/AlternativeSource.php +++ b/lib/internal/Magento/Framework/View/Asset/PreProcessor/AlternativeSource.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\View\Asset\PreProcessor; -use Magento\Framework\Filesystem; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\View\Asset\File\FallbackContext; use Magento\Framework\View\Asset\LockerProcessInterface; diff --git a/lib/internal/Magento/Framework/View/Asset/PropertyGroup.php b/lib/internal/Magento/Framework/View/Asset/PropertyGroup.php index ad86dfca47a25..4fe8f48d6b723 100644 --- a/lib/internal/Magento/Framework/View/Asset/PropertyGroup.php +++ b/lib/internal/Magento/Framework/View/Asset/PropertyGroup.php @@ -45,6 +45,6 @@ public function getProperties() */ public function getProperty($name) { - return isset($this->properties[$name]) ? $this->properties[$name] : null; + return $this->properties[$name] ?? null; } } diff --git a/lib/internal/Magento/Framework/View/BlockPool.php b/lib/internal/Magento/Framework/View/BlockPool.php index c50af9d5bbc5f..dff280057fdaf 100644 --- a/lib/internal/Magento/Framework/View/BlockPool.php +++ b/lib/internal/Magento/Framework/View/BlockPool.php @@ -72,6 +72,6 @@ public function get($name = null) return $this->blocks; } - return isset($this->blocks[$name]) ? $this->blocks[$name] : null; + return $this->blocks[$name] ?? null; } } diff --git a/lib/internal/Magento/Framework/View/DataSourcePool.php b/lib/internal/Magento/Framework/View/DataSourcePool.php index 24bdb6639981b..94f4f1dceae97 100644 --- a/lib/internal/Magento/Framework/View/DataSourcePool.php +++ b/lib/internal/Magento/Framework/View/DataSourcePool.php @@ -80,7 +80,7 @@ public function get($name = null) return $this->dataSources; } - return isset($this->dataSources[$name]) ? $this->dataSources[$name] : null; + return $this->dataSources[$name] ?? null; } /** @@ -107,6 +107,6 @@ public function assign($dataName, $namespace, $alias) */ public function getNamespaceData($namespace) { - return isset($this->assignments[$namespace]) ? $this->assignments[$namespace] : []; + return $this->assignments[$namespace] ?? []; } } diff --git a/lib/internal/Magento/Framework/View/Design/Theme/Validator.php b/lib/internal/Magento/Framework/View/Design/Theme/Validator.php index 06c78e7125040..04e775d730b56 100644 --- a/lib/internal/Magento/Framework/View/Design/Theme/Validator.php +++ b/lib/internal/Magento/Framework/View/Design/Theme/Validator.php @@ -118,7 +118,7 @@ public function addDataValidators($dataKey, $validators) public function getErrorMessages($dataKey = null) { if ($dataKey) { - return isset($this->_errorMessages[$dataKey]) ? $this->_errorMessages[$dataKey] : []; + return $this->_errorMessages[$dataKey] ?? []; } return $this->_errorMessages; } diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php index 0ffa9dce7f730..b2f857bf29f45 100644 --- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php +++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php @@ -1074,7 +1074,7 @@ protected function getCacheLifetime() $cacheLifetime = $this->getData('cache_lifetime'); if (false === $cacheLifetime || null === $cacheLifetime) { - return $cacheLifetime; + return null; } return (int)$cacheLifetime; diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/FileCollector/AggregatedFileCollector.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/FileCollector/AggregatedFileCollector.php index 17a320164dad3..9ba7833a355d7 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Config/FileCollector/AggregatedFileCollector.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Config/FileCollector/AggregatedFileCollector.php @@ -75,7 +75,7 @@ public function collectFiles($searchPattern = null) } $files = $this->collectorAggregated->getFiles($this->design->getDesignTheme(), $searchPattern); foreach ($files as $file) { - $fullFileName = $file->getFileName(); + $fullFileName = $file->getFilename(); $fileDir = dirname($fullFileName); $fileName = basename($fullFileName); $dirRead = $this->readFactory->create($fileDir); diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index 73f125be77c79..48a66b429ebd5 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -213,7 +213,7 @@ public function getFiltersParams() public function getFilterParam($key, $defaultValue = null) { $filter = $this->getFiltersParams(); - return isset($filter[$key]) ? $filter[$key] : $defaultValue; + return $filter[$key] ?? $defaultValue; } /** diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/DataProvider.php b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/DataProvider.php index b2288a47f8f83..baa4e94eed978 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/DataProvider.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/DataProvider/DataProvider.php @@ -181,7 +181,7 @@ public function getMeta() */ public function getFieldSetMetaInfo($fieldSetName) { - return isset($this->meta[$fieldSetName]) ? $this->meta[$fieldSetName] : []; + return $this->meta[$fieldSetName] ?? []; } /** @@ -190,7 +190,7 @@ public function getFieldSetMetaInfo($fieldSetName) */ public function getFieldsMetaInfo($fieldSetName) { - return isset($this->meta[$fieldSetName]['children']) ? $this->meta[$fieldSetName]['children'] : []; + return $this->meta[$fieldSetName]['children'] ?? []; } /** @@ -200,9 +200,7 @@ public function getFieldsMetaInfo($fieldSetName) */ public function getFieldMetaInfo($fieldSetName, $fieldName) { - return isset($this->meta[$fieldSetName]['children'][$fieldName]) - ? $this->meta[$fieldSetName]['children'][$fieldName] - : []; + return $this->meta[$fieldSetName]['children'][$fieldName] ?? []; } /** @@ -291,7 +289,7 @@ public function getData() */ public function getConfigData() { - return isset($this->data['config']) ? $this->data['config'] : []; + return $this->data['config'] ?? []; } /** diff --git a/lib/internal/Magento/Framework/View/Layout.php b/lib/internal/Magento/Framework/View/Layout.php index bd94491537efe..5cd7591098207 100755 --- a/lib/internal/Magento/Framework/View/Layout.php +++ b/lib/internal/Magento/Framework/View/Layout.php @@ -13,7 +13,6 @@ use Magento\Framework\Message\ManagerInterface as MessageManagerInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\View\Layout\Element; -use Magento\Framework\View\Layout\ScheduledStructure; use Psr\Log\LoggerInterface as Logger; /** diff --git a/lib/internal/Magento/Framework/View/Layout/Generic.php b/lib/internal/Magento/Framework/View/Layout/Generic.php index b83545ff3c439..b527d1a817a96 100644 --- a/lib/internal/Magento/Framework/View/Layout/Generic.php +++ b/lib/internal/Magento/Framework/View/Layout/Generic.php @@ -200,6 +200,6 @@ protected function createChildFormComponent(UiComponentInterface $childComponent */ protected function getConfig($name) { - return isset($this->data['config'][$name]) ? $this->data['config'][$name] : null; + return $this->data['config'][$name] ?? null; } } diff --git a/lib/internal/Magento/Framework/View/Layout/ScheduledStructure.php b/lib/internal/Magento/Framework/View/Layout/ScheduledStructure.php index 25a04845a0728..3193e10282fd4 100644 --- a/lib/internal/Magento/Framework/View/Layout/ScheduledStructure.php +++ b/lib/internal/Magento/Framework/View/Layout/ScheduledStructure.php @@ -146,7 +146,7 @@ public function unsetElementToSort($elementName) */ public function getElementToSort($elementName, array $default = []) { - return isset($this->elementsToSort[$elementName]) ? $this->elementsToSort[$elementName] : $default; + return $this->elementsToSort[$elementName] ?? $default; } /** @@ -257,7 +257,7 @@ public function unsetElement($elementName) */ public function getElementToMove($elementName, $default = null) { - return isset($this->scheduledMoves[$elementName]) ? $this->scheduledMoves[$elementName] : $default; + return $this->scheduledMoves[$elementName] ?? $default; } /** @@ -370,7 +370,7 @@ public function unsetStructureElement($elementName) */ public function getStructureElementData($elementName, $default = null) { - return isset($this->scheduledData[$elementName]) ? $this->scheduledData[$elementName] : $default; + return $this->scheduledData[$elementName] ?? $default; } /** @@ -524,6 +524,6 @@ public function populateWithArray(array $data) */ private function getArrayValueByKey($key, array $array) { - return isset($array[$key]) ? $array[$key] : []; + return $array[$key] ?? []; } } diff --git a/lib/internal/Magento/Framework/View/Model/Layout/Merge.php b/lib/internal/Magento/Framework/View/Model/Layout/Merge.php index fe5c94ed21b30..a0cdbfb7d8fe7 100644 --- a/lib/internal/Magento/Framework/View/Model/Layout/Merge.php +++ b/lib/internal/Magento/Framework/View/Model/Layout/Merge.php @@ -443,6 +443,9 @@ public function load($handles = []) if ($result) { $this->addUpdate($result); $this->pageLayout = $this->_loadCache($cacheIdPageLayout); + foreach ($this->getHandles() as $handle) { + $this->allHandles[$handle] = $this->handleProcessed; + } return $this; } @@ -672,7 +675,7 @@ public function getFileLayoutUpdatesXml() $result = $this->_loadXmlString($result); } else { $result = $this->_loadFileLayoutUpdatesXml(); - $this->_saveCache($result->asXml(), $cacheId); + $this->_saveCache($result->asXML(), $cacheId); } $this->layoutUpdatesCache = $result; return $result; diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index d42d30e35cc5b..1a21e61f867d8 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -508,7 +508,7 @@ public function setElementAttribute($elementType, $attribute, $value) public function getElementAttribute($elementType, $attribute) { $this->build(); - return isset($this->elements[$elementType][$attribute]) ? $this->elements[$elementType][$attribute] : null; + return $this->elements[$elementType][$attribute] ?? null; } /** @@ -518,7 +518,7 @@ public function getElementAttribute($elementType, $attribute) public function getElementAttributes($elementType) { $this->build(); - return isset($this->elements[$elementType]) ? $this->elements[$elementType] : []; + return $this->elements[$elementType] ?? []; } /** diff --git a/lib/internal/Magento/Framework/View/Page/Config/Reader/Head.php b/lib/internal/Magento/Framework/View/Page/Config/Reader/Head.php index 8328f03fd6db5..71a2951b40420 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Reader/Head.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Reader/Head.php @@ -138,6 +138,6 @@ private function setMetadata($pageConfigStructure, $node) $metadataName = $node->getAttribute('name'); } - $pageConfigStructure->setMetaData($metadataName, $node->getAttribute('content')); + $pageConfigStructure->setMetadata($metadataName, $node->getAttribute('content')); } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergedTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergedTest.php index 164a2ca4d4d1b..52b45a510e722 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergedTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MergedTest.php @@ -12,6 +12,7 @@ use Magento\Framework\View\Asset\Repository as AssetRepository; use Magento\Framework\View\Asset\MergeableInterface; use Magento\Framework\View\Asset\MergeStrategyInterface; +use Magento\Framework\App\View\Deployment\Version\StorageInterface; /** * Class MergedTest @@ -43,6 +44,11 @@ class MergedTest extends \PHPUnit\Framework\TestCase */ private $assetRepo; + /** + * @var StorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $versionStorage; + protected function setUp() { $this->assetJsOne = $this->getMockForAbstractClass(MergeableInterface::class); @@ -66,6 +72,7 @@ protected function setUp() $this->assetRepo = $this->getMockBuilder(AssetRepository::class) ->disableOriginalConstructor() ->getMock(); + $this->versionStorage = $this->createMock(StorageInterface::class); } /** @@ -74,7 +81,13 @@ protected function setUp() */ public function testConstructorNothingToMerge() { - new \Magento\Framework\View\Asset\Merged($this->logger, $this->mergeStrategy, $this->assetRepo, []); + new \Magento\Framework\View\Asset\Merged( + $this->logger, + $this->mergeStrategy, + $this->assetRepo, + [], + $this->versionStorage + ); } /** @@ -90,6 +103,7 @@ public function testConstructorRequireMergeInterface() 'mergeStrategy' => $this->mergeStrategy, 'assetRepo' => $this->assetRepo, 'assets' => [$this->assetJsOne, $assetUrl], + 'versionStorage' => $this->versionStorage, ]); } @@ -109,6 +123,7 @@ public function testConstructorIncompatibleContentTypes() 'mergeStrategy' => $this->mergeStrategy, 'assetRepo' => $this->assetRepo, 'assets' => [$this->assetJsOne, $assetCss], + 'versionStorage' => $this->versionStorage, ]); } @@ -124,6 +139,7 @@ public function testIteratorInterfaceMerge() 'mergeStrategy' => $this->mergeStrategy, 'assetRepo' => $this->assetRepo, 'assets' => $assets, + 'versionStorage' => $this->versionStorage, ]); $mergedAsset = $this->createMock(\Magento\Framework\View\Asset\File::class); @@ -158,6 +174,7 @@ public function testIteratorInterfaceMergeFailure() 'mergeStrategy' => $this->mergeStrategy, 'assetRepo' => $this->assetRepo, 'assets' => [$this->assetJsOne, $this->assetJsTwo, $assetBroken], + 'versionStorage' => $this->versionStorage, ]); $this->logger->expects($this->once())->method('critical')->with($this->identicalTo($mergeError)); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MinificationTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MinificationTest.php index 1cc2a3dd7e2b7..48fc8d5c775a4 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/MinificationTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/MinificationTest.php @@ -195,6 +195,8 @@ public function isMinifiedFilenameDataProvider() } /** + * Test dev/js/minify_exclude system value as array + * * @return void */ public function testGetExcludes() @@ -213,4 +215,38 @@ public function testGetExcludes() /** check cache: */ $this->assertEquals($expected, $this->minification->getExcludes('js')); } + + /** + * Test dev/js/minify_exclude system value backward compatibility when value was a string + * + * @param string $value + * @param array $expectedValue + * @return void + * + * @dataProvider getExcludesTinyMceAsStringDataProvider + */ + public function testGetExcludesTinyMceAsString(string $value, array $expectedValue) + { + $this->scopeConfigMock + ->expects($this->once()) + ->method('getValue') + ->with('dev/js/minify_exclude') + ->willReturn($value); + + $this->assertEquals($expectedValue, $this->minification->getExcludes('js')); + /** check cache: */ + $this->assertEquals($expectedValue, $this->minification->getExcludes('js')); + } + + /** + * @return array + */ + public function getExcludesTinyMceAsStringDataProvider() + { + return [ + ["/tiny_mce/ \n /tiny_mce2/", ['/tiny_mce/', '/tiny_mce2/']], + ['/tiny_mce/', ['/tiny_mce/']], + [' /tiny_mce/', ['/tiny_mce/']], + ]; + } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php index 70233c0196dc5..5f7508438a6ed 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php @@ -290,9 +290,9 @@ public function getCacheLifetimeDataProvider() 'dataFromCache' => 'dataFromCache', 'dataForSaveCache' => '', 'expectsDispatchEvent' => $this->exactly(2), - 'expectsCacheLoad' => $this->once(), + 'expectsCacheLoad' => $this->never(), 'expectsCacheSave' => $this->never(), - 'expectedResult' => 'dataFromCache', + 'expectedResult' => '', ], [ 'cacheLifetime' => 120, diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php index 4515a29c65e4f..909748722a081 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/Link/CurrentTest.php @@ -57,7 +57,7 @@ public function testIsCurrentIfIsset() /** @var \Magento\Framework\View\Element\Html\Link\Current $link */ $link = $this->_objectManager->getObject(\Magento\Framework\View\Element\Html\Link\Current::class); $link->setCurrent(true); - $this->assertTrue($link->IsCurrent()); + $this->assertTrue($link->isCurrent()); } public function testIsCurrent() diff --git a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php index c0300164e26fe..f8a8939bbfe36 100755 --- a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php @@ -273,7 +273,7 @@ public function testGenerateXml() ->with($this->equalTo([])) ->will($this->returnSelf()); $this->assertSame($this->model, $this->model->generateXml()); - $this->assertSame('<some_update>123</some_update>', $this->model->getNode('some_update')->asXml()); + $this->assertSame('<some_update>123</some_update>', $this->model->getNode('some_update')->asXML()); } public function testGetChildBlock() diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Url/_files/sourceImport.css b/lib/internal/Magento/Framework/View/Test/Unit/Url/_files/sourceImport.css index d7ce9e81258db..420083613705f 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Url/_files/sourceImport.css +++ b/lib/internal/Magento/Framework/View/Test/Unit/Url/_files/sourceImport.css @@ -2,8 +2,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -body {background: url(body.gif);} @import url(../recursive.css); -p {background: url(1.gif?param);} @import url("deep/recursive.css"); +body {background: url(body.gif);} +p {background: url(1.gif?param);} h1 {background: url('../h1.gif#param');} h2 {background: url(../images/h2.gif?test);} diff --git a/lib/internal/Magento/Framework/Webapi/Rest/Request/ParamOverriderInterface.php b/lib/internal/Magento/Framework/Webapi/Rest/Request/ParamOverriderInterface.php index 21741a2c16c1d..828022353e4fa 100644 --- a/lib/internal/Magento/Framework/Webapi/Rest/Request/ParamOverriderInterface.php +++ b/lib/internal/Magento/Framework/Webapi/Rest/Request/ParamOverriderInterface.php @@ -34,6 +34,7 @@ interface ParamOverriderInterface * Returns the overridden value to use. * * @return string|int|null + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getOverriddenValue(); } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php index d8e1a3de3670a..cdb6ed799aade 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php @@ -9,7 +9,6 @@ use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Reflection\MethodsMap; -use Magento\Framework\Webapi\ServicePayloadConverterInterface; /** * Data object converter diff --git a/lib/internal/Magento/Framework/Xml/Generator.php b/lib/internal/Magento/Framework/Xml/Generator.php index 975073e443d0e..f165793c2e2a4 100644 --- a/lib/internal/Magento/Framework/Xml/Generator.php +++ b/lib/internal/Magento/Framework/Xml/Generator.php @@ -146,8 +146,6 @@ public function setIndexedArrayItemName($name) */ protected function _getIndexedArrayItemName() { - return isset($this->_defaultIndexedArrayItemName) - ? $this->_defaultIndexedArrayItemName - : self::DEFAULT_ENTITY_ITEM_NAME; + return $this->_defaultIndexedArrayItemName ?? self::DEFAULT_ENTITY_ITEM_NAME; } } diff --git a/lib/internal/Magento/Framework/Xml/Security.php b/lib/internal/Magento/Framework/Xml/Security.php index e502429e4511a..ec901a63ea862 100644 --- a/lib/internal/Magento/Framework/Xml/Security.php +++ b/lib/internal/Magento/Framework/Xml/Security.php @@ -6,7 +6,6 @@ namespace Magento\Framework\Xml; use DOMDocument; -use Magento\Framework\Phrase; /** * Class Security diff --git a/lib/web/MutationObserver.js b/lib/web/MutationObserver.js index 53424fbfa8d0c..4044aa465e745 100644 --- a/lib/web/MutationObserver.js +++ b/lib/web/MutationObserver.js @@ -324,7 +324,7 @@ if (lastRecord === newRecord) return lastRecord; - // Check if the the record we are adding represents the same record. If + // Check if the record we are adding represents the same record. If // so, we keep the one with the oldValue in it. if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; diff --git a/lib/web/css/docs/actions-toolbar.html b/lib/web/css/docs/actions-toolbar.html index 4a3fbe515ca95..0c2186bf0458c 100644 --- a/lib/web/css/docs/actions-toolbar.html +++ b/lib/web/css/docs/actions-toolbar.html @@ -301,4 +301,4 @@ .example-actions-toolbar-12 { .lib-actions-toolbar-clear-floats(); } -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/breadcrumbs.html b/lib/web/css/docs/breadcrumbs.html index 9ada940b4c0de..e4bfc3a973aa1 100644 --- a/lib/web/css/docs/breadcrumbs.html +++ b/lib/web/css/docs/breadcrumbs.html @@ -502,4 +502,4 @@ border-color: transparent transparent transparent #ccc; } } -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/buttons.html b/lib/web/css/docs/buttons.html index 0890701226d3c..502d35da59a99 100644 --- a/lib/web/css/docs/buttons.html +++ b/lib/web/css/docs/buttons.html @@ -875,4 +875,4 @@ <h2 id="primary-button-big">Primary button big</h2> </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/components.html b/lib/web/css/docs/components.html index 609bd4d3ffa00..9e0b149c989a9 100644 --- a/lib/web/css/docs/components.html +++ b/lib/web/css/docs/components.html @@ -147,4 +147,4 @@ <h1 class="modal-title" data-role="title">Modal Slide</h1> </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/docs.css b/lib/web/css/docs/docs.css index beb18b8b2194a..a3a372ad4eeb9 100644 --- a/lib/web/css/docs/docs.css +++ b/lib/web/css/docs/docs.css @@ -4187,7 +4187,7 @@ select { color: #000066; } .example-message-4:before { - background: #green; + background: green; width: 30px; content: ''; display: block; @@ -4230,7 +4230,7 @@ select { border: 5px solid transparent; height: 0; width: 0; - border-left-color: #green; + border-left-color: green; left: 30px; } .example-message-4 > *:first-child:after { diff --git a/lib/web/css/docs/docs.html b/lib/web/css/docs/docs.html index 164e5a1dfdc21..68e9d98eb2def 100644 --- a/lib/web/css/docs/docs.html +++ b/lib/web/css/docs/docs.html @@ -40,4 +40,4 @@ body { padding: 15px; background-image: none; -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/dropdowns.html b/lib/web/css/docs/dropdowns.html index 6f0c95a723beb..eb9efc9a65078 100644 --- a/lib/web/css/docs/dropdowns.html +++ b/lib/web/css/docs/dropdowns.html @@ -876,4 +876,4 @@ <h2 id="split-button-buttonbutton">Split button: button+button</h2> @_dropdown-split-list-shadow: none, @_dropdown-split-button-border-radius-fix: true ); -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/forms.html b/lib/web/css/docs/forms.html index ca3e2e6184677..211a9bd650ba0 100644 --- a/lib/web/css/docs/forms.html +++ b/lib/web/css/docs/forms.html @@ -1133,4 +1133,4 @@ <h2 id="simple-form-with-required-fields-message">Simple form with "require </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/icons.html b/lib/web/css/docs/icons.html index 1dca9797fc560..d7eeff7f802ca 100644 --- a/lib/web/css/docs/icons.html +++ b/lib/web/css/docs/icons.html @@ -847,4 +847,4 @@ <h2 id="icons-using-sprite">Icons using sprite</h2> } } } -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/index.html b/lib/web/css/docs/index.html index cbf3de48cdfc7..07cd72e6f823f 100644 --- a/lib/web/css/docs/index.html +++ b/lib/web/css/docs/index.html @@ -608,4 +608,4 @@ <h3 id="location">Location</h3> Extends that used in more than one theme should be saved in lib <strong>lib/source/_abstract.less</strong> (will be renamed to _extend.less)</p> <h3 id="naming">Naming</h3> <p>Extend class names should have prefix <strong>.abs-</strong> (from abstract)</p> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/layout.html b/lib/web/css/docs/layout.html index a75e366f621ad..77ed0597f0748 100644 --- a/lib/web/css/docs/layout.html +++ b/lib/web/css/docs/layout.html @@ -340,4 +340,4 @@ <h2 id="three-columns-page-layout">Three columns page layout</h2> </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/lib.html b/lib/web/css/docs/lib.html index ccd5873e94303..001e8aeecd308 100644 --- a/lib/web/css/docs/lib.html +++ b/lib/web/css/docs/lib.html @@ -10,4 +10,4 @@ <p> The _lib.less file contains the includes of all Magento UI library files. To use Magento UI library in your theme add the following directive to the theme’s styles.less:</p> <pre><code class="lang-css"> @import 'source/lib/_lib';</code></pre> <p> The lib.less file is designed to avoid manual adding of each Magento UI library file import instruction to your theme.</p> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/loaders.html b/lib/web/css/docs/loaders.html index 6d63110cfafea..884b71c782905 100644 --- a/lib/web/css/docs/loaders.html +++ b/lib/web/css/docs/loaders.html @@ -182,4 +182,4 @@ </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/messages.html b/lib/web/css/docs/messages.html index 305266906e3d0..287bf1b0d3aa4 100644 --- a/lib/web/css/docs/messages.html +++ b/lib/web/css/docs/messages.html @@ -711,4 +711,4 @@ </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/pages.html b/lib/web/css/docs/pages.html index 8b7335d86b8da..1bbcac94c56f8 100644 --- a/lib/web/css/docs/pages.html +++ b/lib/web/css/docs/pages.html @@ -826,4 +826,4 @@ @_pager-action-color-hover: #fff, @_pager-action-color-active: #fff ); -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/popups.html b/lib/web/css/docs/popups.html index d31d50f6ed7c6..9f7aff4c6569b 100644 --- a/lib/web/css/docs/popups.html +++ b/lib/web/css/docs/popups.html @@ -750,4 +750,4 @@ <h2 id="simple-popup">Simple popup</h2> @_overlay-opacity: .8, @_overlay-opacity-old: 80 ); -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/rating.html b/lib/web/css/docs/rating.html index bf74c47c22f11..3cfe1a864dd98 100644 --- a/lib/web/css/docs/rating.html +++ b/lib/web/css/docs/rating.html @@ -343,4 +343,4 @@ </div><div class="code"><pre><code>.example-rating-summary-7 { .lib-rating-summary(); .lib-rating-summary-label-hide(); -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/resets.html b/lib/web/css/docs/resets.html index f2eb5ee9b48db..fc1cb092422c0 100644 --- a/lib/web/css/docs/resets.html +++ b/lib/web/css/docs/resets.html @@ -34,4 +34,4 @@ <h2 id="global-borderbox">Global border-box</h2> <p> To set <code>box-sizing: border-box</code> globally, use mixin:</p> <pre><code class="lang-CSS"> .lib-set-default-border-box();</code></pre> <p>  </p> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/responsive.html b/lib/web/css/docs/responsive.html index 5e9d3b38eddd0..48d0bd551bd92 100644 --- a/lib/web/css/docs/responsive.html +++ b/lib/web/css/docs/responsive.html @@ -80,4 +80,4 @@ <h2 id="gathering">Gathering</h2> @screen__l: 1024px; @screen__xl: 1440px;</code></pre> <p>  </p> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/sections.html b/lib/web/css/docs/sections.html index c433216775ef4..8ed121578757a 100644 --- a/lib/web/css/docs/sections.html +++ b/lib/web/css/docs/sections.html @@ -643,4 +643,4 @@ </dl></textarea> </div><div class="code"><pre><code>.example-sections-6 { .lib-data-accordion__base(); -}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +}</code></pre></div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/source/_buttons.less b/lib/web/css/docs/source/_buttons.less index a071eed85ef9e..b4b41905b961a 100644 --- a/lib/web/css/docs/source/_buttons.less +++ b/lib/web/css/docs/source/_buttons.less @@ -48,10 +48,10 @@ button { &.example-button-3 { .lib-button-s(); border-radius: 0; - color: #000; + color: @color-black; &:hover, &.active { - color: #000; + color: @color-black; } } } @@ -524,10 +524,10 @@ button { } &.example-button-6 { .lib-button-s(); - color: #fff; + color: @color-white; &:hover, &.active { - color: #fff; + color: @color-white; } } } @@ -721,13 +721,13 @@ button { @_button-padding: @button__padding, @_button-gradient-color-start: #1979c3, @_button-gradient-color-end: #006bb4, - @_button-color: #fff, + @_button-color: @color-white, @_button-gradient-color-start-hover: #006bb4, @_button-gradient-color-end-hover: #1979c3, - @_button-color-hover: #fff, + @_button-color-hover: @color-white, @_button-gradient-color-start-active: #006bb4, @_button-gradient-color-end-active: #006bb4, - @_button-color-active: #fff, + @_button-color-active: @color-white, @_button-gradient: true, @_button-gradient-direction: vertical, @_button-border: @button-primary__border, diff --git a/lib/web/css/docs/source/_messages.less b/lib/web/css/docs/source/_messages.less index 45d4538d14aff..91b3f280880ca 100644 --- a/lib/web/css/docs/source/_messages.less +++ b/lib/web/css/docs/source/_messages.less @@ -176,7 +176,7 @@ // ``` // -@message-custom__color: #000; +@message-custom__color: @color-black; @message-custom__background: #fc0; @message-custom__border-color: orange; @@ -185,7 +185,7 @@ @message-custom-link__color-active: darken(@message-custom-link__color, 30%); @message-custom-icon: @icon-settings; -@message-custom-icon__color-lateral: #000; +@message-custom-icon__color-lateral: @color-black; @message-custom-icon__background: #green; @message-custom-icon__top: 15px; @message-custom-icon__right: false; diff --git a/lib/web/css/docs/source/_pages.less b/lib/web/css/docs/source/_pages.less index 0b94f84e54f47..6b01fa7549e92 100644 --- a/lib/web/css/docs/source/_pages.less +++ b/lib/web/css/docs/source/_pages.less @@ -852,22 +852,22 @@ .example-pages-3 { .lib-pager( @_pager-label-display: none, - @_pager-color: #fff, + @_pager-color: @color-white, @_pager-background: @link__color, - @_pager-color-visited: #fff, + @_pager-color-visited: @color-white, @_pager-background-visited: @link__visited__color, - @_pager-color-hover: #fff, + @_pager-color-hover: @color-white, @_pager-background-hover: @link__hover__color, - @_pager-color-active: #fff, + @_pager-color-active: @color-white, @_pager-background-active: @link__active__color, - @_pager-current-color: #fff, + @_pager-current-color: @color-white, @_pager-current-background: @link__visited__color, @_pager-action-background: @link__color, @_pager-action-background-visited: @link__visited__color, @_pager-action-background-hover: @link__hover__color, @_pager-action-background-active: @link__active__color, - @_pager-action-color: #fff, - @_pager-action-color-hover: #fff, - @_pager-action-color-active: #fff + @_pager-action-color: @color-white, + @_pager-action-color-hover: @color-white, + @_pager-action-color-active: @color-white ); } diff --git a/lib/web/css/docs/source/_tables.less b/lib/web/css/docs/source/_tables.less index 9d0df2350cee9..8feec72f33699 100644 --- a/lib/web/css/docs/source/_tables.less +++ b/lib/web/css/docs/source/_tables.less @@ -677,7 +677,7 @@ .example-table-5 { .lib-table(); .lib-table-background-color( - @_table-background-color: #fff, + @_table-background-color: @color-white, @_table-head-background-color: #ccf, @_table-foot-background-color: #cff, @_table-td-background-color: #fcc, @@ -1253,7 +1253,7 @@ .lib-table(); .lib-table-striped( @_stripped-background-color: #ffc, - @_stripped-color: #000, + @_stripped-color: @color-black, @_stripped-direction: horizontal, @_stripped-highlight: even ); diff --git a/lib/web/css/docs/source/_variables.less b/lib/web/css/docs/source/_variables.less index 326580ead813f..e1845786067c8 100644 --- a/lib/web/css/docs/source/_variables.less +++ b/lib/web/css/docs/source/_variables.less @@ -7375,21 +7375,3 @@ // </tr> // </table> // </pre> -// -// #### <code>.lib-url-check()</code> mixin variables -// <pre> -// <table> -// <tr> -// <th class="vars_head">Mixin variable</th> -// <th class="vars_head">Allowed values</th> -// <th class="vars_head">Output variable</th> -// <th class="vars_head">Comment</th> -// </tr> -// <tr> -// <th>@_path</th> -// <td class="vars_value">'' | false | value</td> -// <td class="vars_value">@lib-url-check-output</td> -// <td>Passed url to wrap in 'url( ... )'. If the 'false' value passed mixin will return 'false'</td> -// </tr> -// </table> -// </pre> diff --git a/lib/web/css/docs/tables.html b/lib/web/css/docs/tables.html index 5a5067ebd0e4b..280f32135de62 100644 --- a/lib/web/css/docs/tables.html +++ b/lib/web/css/docs/tables.html @@ -1498,4 +1498,4 @@ </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/tooltips.html b/lib/web/css/docs/tooltips.html index 20158046859f6..2b38aa0a55aef 100644 --- a/lib/web/css/docs/tooltips.html +++ b/lib/web/css/docs/tooltips.html @@ -186,4 +186,4 @@ </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/typography.html b/lib/web/css/docs/typography.html index 76f704c17c1bd..a933bcb46bf64 100644 --- a/lib/web/css/docs/typography.html +++ b/lib/web/css/docs/typography.html @@ -1686,4 +1686,4 @@ </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/utilities.html b/lib/web/css/docs/utilities.html index 29ecce2414e1e..8ab4a1136967e 100644 --- a/lib/web/css/docs/utilities.html +++ b/lib/web/css/docs/utilities.html @@ -261,50 +261,4 @@ </tr> </table> </pre> -</div></article><article id="liburlcheck" class="section"><div class="docs"><a href="#liburlcheck" class="permalink"><svg viewBox="0 0 512 512" height="32" width="32" class="icon"><path d="M156.2,199.7c7.5-7.5,15.9-13.8,24.8-18.7c49.6-27.3,113.1-12.8,145,35.5l-38.5,38.5c-11.1-25.2-38.5-39.6-65.8-33.5c-10.3,2.3-20.1,7.4-28,15.4l-73.9,73.9c-22.4,22.4-22.4,58.9,0,81.4c22.4,22.4,58.9,22.4,81.4,0l22.8-22.8c20.7,8.2,42.9,11.5,64.9,9.9l-50.3,50.3c-43.1,43.1-113,43.1-156.1,0c-43.1-43.1-43.1-113-0-156.1L156.2,199.7z M273.6,82.3l-50.3,50.3c21.9-1.6,44.2,1.6,64.9,9.9l22.8-22.8c22.4-22.4,58.9-22.4,81.4,0c22.4,22.4,22.4,58.9,0,81.4l-73.9,73.9c-22.5,22.5-59.1,22.3-81.4,0c-5.2-5.2-9.7-11.7-12.5-18l-38.5,38.5c4,6.1,8.3,11.5,13.7,16.9c13.9,13.9,31.7,24.3,52.1,29.3c26.5,6.4,54.8,2.8,79.2-10.6c8.9-4.9,17.3-11.1,24.8-18.7l73.9-73.9c43.1-43.1,43.1-113,0-156.1C386.6,39.2,316.7,39.2,273.6,82.3z"></path></svg></a><h1 id="liburlcheck">.lib-url-check()</h1> -<p> The <code>.lib-url-check()</code> mixin wraps passed value with 'url( ... )' and returns <code>@lib-url-check-output</code> variable. Can be used with <code>.lib-css()</code> mixin.</p> -<p> If the variable is set to <code>false</code>, the <code>.lib-url-check()</code> will return false.</p> -<textarea class="preview-code" spellcheck="false"> <div class="example-url-check"> - Block with background. - </div></textarea><textarea class="preview-code" spellcheck="false"> <div class="example-url-check-false"> - Block with no background. - </div></textarea> -</div><div class="code"><pre><code>.example-url-check { - // Set image path variable - @_icon-image: '/images/test.png'; - - // "Call" the mixin - .lib-url-check(@_icon-image); - - // Will return url('/images/test.png') - .lib-css(background, #eee @lib-url-check-output no-repeat 0 0); -} - - -.example-url-check-false { - // Set usage image path to false - @_icon-image: false; - - // "Call" the mixin - .lib-url-check(@_icon-image); - - // Will return 'false' and outputs nothing - .lib-css(background, #eee @lib-url-check-output no-repeat 0 0); -}</code></pre></div></article><article id="liburlcheck-variables" class="section"><div class="docs"><a href="#liburlcheck-variables" class="permalink"><svg viewBox="0 0 512 512" height="32" width="32" class="icon"><path d="M156.2,199.7c7.5-7.5,15.9-13.8,24.8-18.7c49.6-27.3,113.1-12.8,145,35.5l-38.5,38.5c-11.1-25.2-38.5-39.6-65.8-33.5c-10.3,2.3-20.1,7.4-28,15.4l-73.9,73.9c-22.4,22.4-22.4,58.9,0,81.4c22.4,22.4,58.9,22.4,81.4,0l22.8-22.8c20.7,8.2,42.9,11.5,64.9,9.9l-50.3,50.3c-43.1,43.1-113,43.1-156.1,0c-43.1-43.1-43.1-113-0-156.1L156.2,199.7z M273.6,82.3l-50.3,50.3c21.9-1.6,44.2,1.6,64.9,9.9l22.8-22.8c22.4-22.4,58.9-22.4,81.4,0c22.4,22.4,22.4,58.9,0,81.4l-73.9,73.9c-22.5,22.5-59.1,22.3-81.4,0c-5.2-5.2-9.7-11.7-12.5-18l-38.5,38.5c4,6.1,8.3,11.5,13.7,16.9c13.9,13.9,31.7,24.3,52.1,29.3c26.5,6.4,54.8,2.8,79.2-10.6c8.9-4.9,17.3-11.1,24.8-18.7l73.9-73.9c43.1-43.1,43.1-113,0-156.1C386.6,39.2,316.7,39.2,273.6,82.3z"></path></svg></a><h1 id="liburlcheck-variables">.lib-url-check() variables</h1> - <pre> - <table> - <tr> - <th class="vars_head">Mixin variable</th> - <th class="vars_head">Allowed values</th> - <th class="vars_head">Output variable</th> - <th class="vars_head">Comment</th> - </tr> - <tr> - <th>@_path</th> - <td class="vars_value">'' | false | value</td> - <td class="vars_value">@lib-url-check-output</td> - <td>Passed url to wrap in 'url( ... )'. If the 'false' value passed mixin will return 'false'</td> - </tr> - </table> - </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/docs/variables.html b/lib/web/css/docs/variables.html index 4423d682d0f80..4f353dc1554a4 100644 --- a/lib/web/css/docs/variables.html +++ b/lib/web/css/docs/variables.html @@ -7391,4 +7391,4 @@ <h4 id="codeliburlcheckcode-mixin-variables"><code>.lib-url-check()</code> mixin </tr> </table> </pre> -</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:".lib-url-check()",filename:"utilities",url:"utilities.html#liburlcheck"},{title:".lib-url-check() variables",filename:"utilities",url:"utilities.html#liburlcheck-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> +</div></article></section><div class="bar bottom"><div hidden class="settings container"><!-- Icons from http://iconmonstr.com--><button title="Desktop (1280)" data-width='1280'><svg viewBox="0 0 412 386" height="24" width="26" class="icon"><path d="m147.6,343.9c-4.5,15.9-26.2,37.6-42.1,42.1h201c-15.3,-4-38.1,-26.8-42.1,-42.1H147.6zM387,0.5H25c-13.8,0-25,11.2-25,25V294c0,13.8 11.2,25 25,25h362c13.8,0 25,-11.2 25,-25V25.5C412,11.7 400.8,0.5 387,0.5zM369.9,238.2H42.1L42.1,42.6 369.9,42.6V238.2z"></path></svg></button><button title="Laptop (1024)" data-width='1024'><svg viewBox="0 0 384 312" height="23" width="28" class="icon"><path d="m349.2,20.5c0,-11-9,-20-20,-20H53.6c-11,0-20,9-20,20v194H349.2v-194zm-27,167H60.6V27.5H322.2v160zm28,42H32.6L2.6,282.1c-3.5,6.2-3.5,13.8 0.1,19.9 3.6,6.2 10.2,9.9 17.3,9.9H363.1c7.1,0 13.7,-3.8 17.3,-10 3.6,-6.2 3.6,-13.8 0,-20l-30.2,-52.5zm-196.9,54 8,-23.5h60.5l8,23.5h-76.5z"></path></svg></button><button title="Tablet (768)" data-width='768'><svg viewBox="0 0 317 412" height="24" width="18" class="icon"><path d="M 316.5,380 V 32 c 0,-17.7 -14.3,-32 -32,-32 H 32 C 14.3,0 0,14.3 0,32 v 348 c 0,17.7 14.3,32 32,32 h 252.5 c 17.7,0 32,-14.3 32,-32 z M 40,367 V 45 H 276.5 V 367 H 40 z m 109.8,22.7 c 0,-4.7 3.8,-8.5 8.5,-8.5 4.7,0 8.5,3.8 8.5,8.5 0,4.7 -3.8,8.5 -8.5,8.5 -4.7,0 -8.5,-3.8 -8.5,-8.5 z"></path></svg></button><button title="Smart phone (320)" data-width='320'><svg viewBox="0 0 224 412" height="24" width="13" class="icon"><path d="M 190.7,0 H 33 C 14.8,0 0,14.8 0,33 v 346 c 0,18.2 14.8,33 33,33 h 157.7 c 18.2,0 33,-14.8 33,-33 V 33 c 0,-18.2 -14.8,-33 -33,-33 z M 94.3,30.2 h 37 c 2.2,0 4,1.8 4,4 0,2.2 -1.8,4 -4,4 h -37 c -2.2,0 -4,-1.8 -4,-4 0,-2.2 1.8,-4 4,-4 z m 18.5,362.8 c -8.8,0 -16,-7.2 -16,-16 0,-8.8 7.2,-16 16,-16 8.8,0 16,7.2 16,16 0,8.8 -7.2,16 -16,16 z M 198.6,343.8 H 25.1 V 68.2 h 173.5 v 275.5 z"></path></svg></button><button title="Feature phone (240)" data-width='240'><svg viewBox="0 0 201 412" height="24" width="12" class="icon"><path d="M 165.5,0.2 V 45 H 25 c -13.8,0 -25,11.2 -25,25 V 387 c 0,13.8 11.2,25 25,25 h 150.5 c 13.8,0 25,-11.2 25,-25 V 0.2 h -35 z M 65.2,366.5 H 34.2 v -24.5 h 31 v 24.5 z m 0,-44.3 H 34.2 v -24.5 h 31 v 24.5 z m 50.5,44.3 H 84.7 v -24.5 h 31 v 24.5 z m 0,-44.3 H 84.7 v -24.5 h 31 v 24.5 z m 50.5,44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-44.3 h -31 v -24.5 h 31 v 24.5 z m 0,-59.3 h -132 V 95.4 h 132 V 262.9 z"></path></svg></button><button title="Auto (100%)" data-width="auto" class="auto is-active">Auto</button></div></div><script>(function(){var a=[{title:"actions-toolbar",filename:"actions-toolbar",url:"actions-toolbar.html"},{title:"Actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar"},{title:"Actions toolbar mixin variables",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-mixin-variables"},{title:"Actions toolbar alignment",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-alignment"},{title:"Reverse primary and secondary blocks",filename:"actions-toolbar",url:"actions-toolbar.html#reverse-primary-and-secondary-blocks"},{title:"Actions toolbar indents customizations",filename:"actions-toolbar",url:"actions-toolbar.html#actions-toolbar-indents-customizations"},{title:"Responsive actions toolbar",filename:"actions-toolbar",url:"actions-toolbar.html#responsive-actions-toolbar"},{title:"breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html"},{title:"Breadcrumbs",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs"},{title:"Breadcrumbs variables",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-variables"},{title:"Button-styled breadcrumbs with gradient background, border, and no separating symbol",filename:"breadcrumbs",url:"breadcrumbs.html#buttonstyled-breadcrumbs-with-gradient-background-border-and-no-separating-symbol"},{title:"Breadcrumbs with solid background",filename:"breadcrumbs",url:"breadcrumbs.html#breadcrumbs-with-solid-background"},{title:"buttons",filename:"buttons",url:"buttons.html"},{title:"Default button",filename:"buttons",url:"buttons.html#default-button"},{title:"Button variables",filename:"buttons",url:"buttons.html#button-variables"},{title:"Button as an icon",filename:"buttons",url:"buttons.html#button-as-an-icon"},{title:"Button with an icon on the left or right side of the text",filename:"buttons",url:"buttons.html#button-with-an-icon-on-the-left-or-right-side-of-the-text"},{title:"Button with fixed width",filename:"buttons",url:"buttons.html#button-with-fixed-width"},{title:"Primary button",filename:"buttons",url:"buttons.html#primary-button"},{title:"Primary button variables",filename:"buttons",url:"buttons.html#primary-button-variables"},{title:"Button with gradient background",filename:"buttons",url:"buttons.html#button-with-gradient-background"},{title:"Button as a link",filename:"buttons",url:"buttons.html#button-as-a-link"},{title:"Button as a link variables",filename:"buttons",url:"buttons.html#button-as-a-link-variables"},{title:"Link as a button",filename:"buttons",url:"buttons.html#link-as-a-button"},{title:"Button reset",filename:"buttons",url:"buttons.html#button-reset"},{title:"Button revert secondary color",filename:"buttons",url:"buttons.html#button-revert-secondary-color"},{title:"Button revert secondary color variables",filename:"buttons",url:"buttons.html#button-revert-secondary-color-variables"},{title:"Button revert secondary size",filename:"buttons",url:"buttons.html#button-revert-secondary-size"},{title:"Button revert secondary size variables",filename:"buttons",url:"buttons.html#button-revert-secondary-size-variables"},{title:"components",filename:"components",url:"components.html"},{title:"Components",filename:"components",url:"components.html#components"},{title:"Components Variables",filename:"components",url:"components.html#components-variables"},{title:"dropdowns",filename:"dropdowns",url:"dropdowns.html"},{title:"Drop-down and split buttons mixins",filename:"dropdowns",url:"dropdowns.html#dropdown-and-split-buttons-mixins"},{title:"Drop-down",filename:"dropdowns",url:"dropdowns.html#dropdown"},{title:"Drop-down variables",filename:"dropdowns",url:"dropdowns.html#dropdown-variables"},{title:"Drop-down with icon customization",filename:"dropdowns",url:"dropdowns.html#dropdown-with-icon-customization"},{title:"Modify dropdown list styles",filename:"dropdowns",url:"dropdowns.html#modify-dropdown-list-styles"},{title:"Split button",filename:"dropdowns",url:"dropdowns.html#split-button"},{title:"Split button variables",filename:"dropdowns",url:"dropdowns.html#split-button-variables"},{title:"Split button - button styling",filename:"dropdowns",url:"dropdowns.html#split-button-button-styling"},{title:"Split button icon customization",filename:"dropdowns",url:"dropdowns.html#split-button-icon-customization"},{title:"Split button drop-down list customization",filename:"dropdowns",url:"dropdowns.html#split-button-dropdown-list-customization"},{title:"forms",filename:"forms",url:"forms.html"},{title:"Forms mixins",filename:"forms",url:"forms.html#forms-mixins"},{title:"Global forms elements customization",filename:"forms",url:"forms.html#global-forms-elements-customization"},{title:"Fieldsets & fields customization",filename:"forms",url:"forms.html#fieldsets-fields-customization"},{title:"Fieldset and legend customization variables",filename:"forms",url:"forms.html#fieldset-and-legend-customization-variables"},{title:"Fields customization variables",filename:"forms",url:"forms.html#fields-customization-variables"},{title:"Required fields message customization variables",filename:"forms",url:"forms.html#required-fields-message-customization-variables"},{title:"Form element inputs customization",filename:"forms",url:"forms.html#form-element-inputs-customization"},{title:"Form element inputs customization variables",filename:"forms",url:"forms.html#form-element-inputs-customization-variables"},{title:"Form element choice",filename:"forms",url:"forms.html#form-element-choice"},{title:"Form element choice variables",filename:"forms",url:"forms.html#form-element-choice-variables"},{title:"Custom color",filename:"forms",url:"forms.html#custom-color"},{title:"Input number - input-text view",filename:"forms",url:"forms.html#input-number-inputtext-view"},{title:"Input search - input-text view",filename:"forms",url:"forms.html#input-search-inputtext-view"},{title:"Form validation",filename:"forms",url:"forms.html#form-validation"},{title:"Form validation variables",filename:"forms",url:"forms.html#form-validation-variables"},{title:"icons",filename:"icons",url:"icons.html"},{title:"Icons",filename:"icons",url:"icons.html#icons"},{title:"Icon with image or sprite",filename:"icons",url:"icons.html#icon-with-image-or-sprite"},{title:"Icon with image or sprite variables",filename:"icons",url:"icons.html#icon-with-image-or-sprite-variables"},{title:"Icon position for an icon with image or sprite",filename:"icons",url:"icons.html#icon-position-for-an-icon-with-image-or-sprite"},{title:"Position for icon with image or sprite mixin variables",filename:"icons",url:"icons.html#position-for-icon-with-image-or-sprite-mixin-variables"},{title:"Icon sprite position (with grid)",filename:"icons",url:"icons.html#icon-sprite-position-with-grid"},{title:"Icon sprite position variables",filename:"icons",url:"icons.html#icon-sprite-position-variables"},{title:"Image/sprite icon size",filename:"icons",url:"icons.html#imagesprite-icon-size"},{title:"Image/sprite icon size variables",filename:"icons",url:"icons.html#imagesprite-icon-size-variables"},{title:"Font icon",filename:"icons",url:"icons.html#font-icon"},{title:"Font icon variables",filename:"icons",url:"icons.html#font-icon-variables"},{title:"Change the size of font icon",filename:"icons",url:"icons.html#change-the-size-of-font-icon"},{title:"Change the size of font icon variables",filename:"icons",url:"icons.html#change-the-size-of-font-icon-variables"},{title:"Hide icon text",filename:"icons",url:"icons.html#hide-icon-text"},{title:"Sprite and font icons for Blank theme",filename:"icons",url:"icons.html#sprite-and-font-icons-for-blank-theme"},{title:"layout",filename:"layout",url:"layout.html"},{title:"Layout",filename:"layout",url:"layout.html#layout"},{title:"Layout global variables",filename:"layout",url:"layout.html#layout-global-variables"},{title:"Page layouts",filename:"layout",url:"layout.html#page-layouts"},{title:"Layout column",filename:"layout",url:"layout.html#layout-column"},{title:"Layout column variables",filename:"layout",url:"layout.html#layout-column-variables"},{title:"Layout width",filename:"layout",url:"layout.html#layout-width"},{title:"Layout width variables",filename:"layout",url:"layout.html#layout-width-variables"},{title:"lib",filename:"lib",url:"lib.html"},{title:"Including Magento UI library to your theme",filename:"lib",url:"lib.html#including-magento-ui-library-to-your-theme"},{title:"loaders",filename:"loaders",url:"loaders.html"},{title:"Loaders",filename:"loaders",url:"loaders.html#loaders"},{title:"Default loader variables",filename:"loaders",url:"loaders.html#default-loader-variables"},{title:"Loading",filename:"loaders",url:"loaders.html#loading"},{title:"Loading default variables",filename:"loaders",url:"loaders.html#loading-default-variables"},{title:"messages",filename:"messages",url:"messages.html"},{title:"Messages",filename:"messages",url:"messages.html#messages"},{title:"Information message",filename:"messages",url:"messages.html#information-message"},{title:"Warning message",filename:"messages",url:"messages.html#warning-message"},{title:"Error message",filename:"messages",url:"messages.html#error-message"},{title:"Success message",filename:"messages",url:"messages.html#success-message"},{title:"Notice message",filename:"messages",url:"messages.html#notice-message"},{title:"Message with inner icon",filename:"messages",url:"messages.html#message-with-inner-icon"},{title:"Message with lateral icon",filename:"messages",url:"messages.html#message-with-lateral-icon"},{title:"Custom message style",filename:"messages",url:"messages.html#custom-message-style"},{title:"Messages global variables",filename:"messages",url:"messages.html#messages-global-variables"},{title:"pages",filename:"pages",url:"pages.html"},{title:"Pagination HTML markup",filename:"pages",url:"pages.html#pagination-html-markup"},{title:"Pagination variables",filename:"pages",url:"pages.html#pagination-variables"},{title:"Pagination with label and gradient background on links",filename:"pages",url:"pages.html#pagination-with-label-and-gradient-background-on-links"},{title:'Pagination with "previous"..."next" text links and label',filename:"pages",url:"pages.html#pagination-with-previousnext-text-links-and-label"},{title:"Pagination without label, with solid background",filename:"pages",url:"pages.html#pagination-without-label-with-solid-background"},{title:"popups",filename:"popups",url:"popups.html"},{title:"Popups",filename:"popups",url:"popups.html#popups"},{title:"Popup variables",filename:"popups",url:"popups.html#popup-variables"},{title:"Window overlay mixin variables",filename:"popups",url:"popups.html#window-overlay-mixin-variables"},{title:"Fixed height popup",filename:"popups",url:"popups.html#fixed-height-popup"},{title:"Fixed content height popup",filename:"popups",url:"popups.html#fixed-content-height-popup"},{title:"Margins for header, content and footer block in popup",filename:"popups",url:"popups.html#margins-for-header-content-and-footer-block-in-popup"},{title:"Popup titles styled as theme headings",filename:"popups",url:"popups.html#popup-titles-styled-as-theme-headings"},{title:"Popup action toolbar",filename:"popups",url:"popups.html#popup-action-toolbar"},{title:"Popup Close button without an icon",filename:"popups",url:"popups.html#popup-close-button-without-an-icon"},{title:"Modify the icon of popup Close button",filename:"popups",url:"popups.html#modify-the-icon-of-popup-close-button"},{title:"Modify overlay styles",filename:"popups",url:"popups.html#modify-overlay-styles"},{title:"rating",filename:"rating",url:"rating.html"},{title:"Ratings",filename:"rating",url:"rating.html#ratings"},{title:"Global rating variables",filename:"rating",url:"rating.html#global-rating-variables"},{title:"Rating with vote",filename:"rating",url:"rating.html#rating-with-vote"},{title:"Rating with vote icons number customization",filename:"rating",url:"rating.html#rating-with-vote-icons-number-customization"},{title:"Rating with vote icons colors customization",filename:"rating",url:"rating.html#rating-with-vote-icons-colors-customization"},{title:"Rating with vote icons symbol customization",filename:"rating",url:"rating.html#rating-with-vote-icons-symbol-customization"},{title:"Accessible rating with vote",filename:"rating",url:"rating.html#accessible-rating-with-vote"},{title:"Rating summary",filename:"rating",url:"rating.html#rating-summary"},{title:"Rating summary icons number customization",filename:"rating",url:"rating.html#rating-summary-icons-number-customization"},{title:"Rating summary icons color customization",filename:"rating",url:"rating.html#rating-summary-icons-color-customization"},{title:"Rating summary icons symbol customization",filename:"rating",url:"rating.html#rating-summary-icons-symbol-customization"},{title:"Rating summary hide label",filename:"rating",url:"rating.html#rating-summary-hide-label"},{title:"Rating summary multiple ratings",filename:"rating",url:"rating.html#rating-summary-multiple-ratings"},{title:"Rating hide label mixin",filename:"rating",url:"rating.html#rating-hide-label-mixin"},{title:"resets",filename:"resets",url:"resets.html"},{title:"Resets",filename:"resets",url:"resets.html#resets"},{title:"responsive",filename:"responsive",url:"responsive.html"},{title:"Responsive",filename:"responsive",url:"responsive.html#responsive"},{title:"Responsive mixins usage",filename:"responsive",url:"responsive.html#responsive-mixins-usage"},{title:"Media query style groups separation variables",filename:"responsive",url:"responsive.html#media-query-style-groups-separation-variables"},{title:"Responsive breakpoints",filename:"responsive",url:"responsive.html#responsive-breakpoints"},{title:"sections",filename:"sections",url:"sections.html"},{title:"Tabs and accordions",filename:"sections",url:"sections.html#tabs-and-accordions"},{title:"Tabs",filename:"sections",url:"sections.html#tabs"},{title:"Tabs mixin variables",filename:"sections",url:"sections.html#tabs-mixin-variables"},{title:"Tabs with content top border",filename:"sections",url:"sections.html#tabs-with-content-top-border"},{title:"Accordion",filename:"sections",url:"sections.html#accordion"},{title:"Accordion mixin variables",filename:"sections",url:"sections.html#accordion-mixin-variables"},{title:"Responsive tabs",filename:"sections",url:"sections.html#responsive-tabs"},{title:"Tabs Base",filename:"sections",url:"sections.html#tabs-base"},{title:"Accordion Base",filename:"sections",url:"sections.html#accordion-base"},{title:"tables",filename:"tables",url:"tables.html"},{title:"Tables",filename:"tables",url:"tables.html#tables"},{title:"Table mixin variables",filename:"tables",url:"tables.html#table-mixin-variables"},{title:"Table typography",filename:"tables",url:"tables.html#table-typography"},{title:"Table typography mixin variables",filename:"tables",url:"tables.html#table-typography-mixin-variables"},{title:"Table caption",filename:"tables",url:"tables.html#table-caption"},{title:"Table caption mixin variables",filename:"tables",url:"tables.html#table-caption-mixin-variables"},{title:"Table cells resize",filename:"tables",url:"tables.html#table-cells-resize"},{title:"Table cells resize variables",filename:"tables",url:"tables.html#table-cells-resize-variables"},{title:"Table background customization",filename:"tables",url:"tables.html#table-background-customization"},{title:"Table background mixin variables",filename:"tables",url:"tables.html#table-background-mixin-variables"},{title:"Table borders customization",filename:"tables",url:"tables.html#table-borders-customization"},{title:"Table borders mixin variables",filename:"tables",url:"tables.html#table-borders-mixin-variables"},{title:"Table with horizontal borders",filename:"tables",url:"tables.html#table-with-horizontal-borders"},{title:"Table with vertical borders",filename:"tables",url:"tables.html#table-with-vertical-borders"},{title:"Table with light borders",filename:"tables",url:"tables.html#table-with-light-borders"},{title:"Table without borders",filename:"tables",url:"tables.html#table-without-borders"},{title:"Striped table",filename:"tables",url:"tables.html#striped-table"},{title:"Striped table mixin variables",filename:"tables",url:"tables.html#striped-table-mixin-variables"},{title:"Table with rows hover",filename:"tables",url:"tables.html#table-with-rows-hover"},{title:"Table with rows hover mixin variables",filename:"tables",url:"tables.html#table-with-rows-hover-mixin-variables"},{title:"Responsive table technics #1",filename:"tables",url:"tables.html#responsive-table-technics-1"},{title:"Responsive table technics #2",filename:"tables",url:"tables.html#responsive-table-technics-2"},{title:"Responsive table technics #2 mixin variables",filename:"tables",url:"tables.html#responsive-table-technics-2-mixin-variables"},{title:"tooltips",filename:"tooltips",url:"tooltips.html"},{title:"Tooltips",filename:"tooltips",url:"tooltips.html#tooltips"},{title:"Tooltips variables",filename:"tooltips",url:"tooltips.html#tooltips-variables"},{title:"typography",filename:"typography",url:"typography.html"},{title:"Typogrphy",filename:"typography",url:"typography.html#typogrphy"},{title:"Typography variables",filename:"typography",url:"typography.html#typography-variables"},{title:"Font-size mixin",filename:"typography",url:"typography.html#fontsize-mixin"},{title:"Line-height mixin",filename:"typography",url:"typography.html#lineheight-mixin"},{title:"Word breaking mixin",filename:"typography",url:"typography.html#word-breaking-mixin"},{title:"Font face mixin",filename:"typography",url:"typography.html#font-face-mixin"},{title:"Text overflow mixin",filename:"typography",url:"typography.html#text-overflow-mixin"},{title:"Text hide",filename:"typography",url:"typography.html#text-hide"},{title:"Hyphens",filename:"typography",url:"typography.html#hyphens"},{title:"Font style and color",filename:"typography",url:"typography.html#font-style-and-color"},{title:"Font style mixin variables",filename:"typography",url:"typography.html#font-style-mixin-variables"},{title:"Reset list styles",filename:"typography",url:"typography.html#reset-list-styles"},{title:"Reset list styles variables",filename:"typography",url:"typography.html#reset-list-styles-variables"},{title:"Inline-block list item styling",filename:"typography",url:"typography.html#inlineblock-list-item-styling"},{title:"Link styling mixin",filename:"typography",url:"typography.html#link-styling-mixin"},{title:"Link styling mixin variables",filename:"typography",url:"typography.html#link-styling-mixin-variables"},{title:"Heading styling mixin",filename:"typography",url:"typography.html#heading-styling-mixin"},{title:"Base typography mixins",filename:"typography",url:"typography.html#base-typography-mixins"},{title:"Base typography mixin variables",filename:"typography",url:"typography.html#base-typography-mixin-variables"},{title:"Headings typography mixin",filename:"typography",url:"typography.html#headings-typography-mixin"},{title:"Headings typography mixin variables",filename:"typography",url:"typography.html#headings-typography-mixin-variables"},{title:"Typography links mixin",filename:"typography",url:"typography.html#typography-links-mixin"},{title:"Typography lists mixin",filename:"typography",url:"typography.html#typography-lists-mixin"},{title:"Typography lists mixin variables",filename:"typography",url:"typography.html#typography-lists-mixin-variables"},{title:"Typography code elements mixin",filename:"typography",url:"typography.html#typography-code-elements-mixin"},{title:"Typography code mixin variables",filename:"typography",url:"typography.html#typography-code-mixin-variables"},{title:"Typography blockquote",filename:"typography",url:"typography.html#typography-blockquote"},{title:"Typography blockquote mixin variables",filename:"typography",url:"typography.html#typography-blockquote-mixin-variables"},{title:"utilities",filename:"utilities",url:"utilities.html"},{title:"Utilities",filename:"utilities",url:"utilities.html#utilities"},{title:".lib-clearfix()",filename:"utilities",url:"utilities.html#libclearfix"},{title:".lib-visibility-hidden()",filename:"utilities",url:"utilities.html#libvisibilityhidden"},{title:".lib-visually-hidden()",filename:"utilities",url:"utilities.html#libvisuallyhidden"},{title:".lib-visually-hidden-reset()",filename:"utilities",url:"utilities.html#libvisuallyhiddenreset"},{title:".lib-css()",filename:"utilities",url:"utilities.html#libcss"},{title:".lib-css() variables",filename:"utilities",url:"utilities.html#libcss-variables"},{title:".lib-rotate()",filename:"utilities",url:"utilities.html#librotate"},{title:".lib-rotate() variables",filename:"utilities",url:"utilities.html#librotate-variables"},{title:".lib-input-placeholder()",filename:"utilities",url:"utilities.html#libinputplaceholder"},{title:".lib-input-placeholder() variables",filename:"utilities",url:"utilities.html#libinputplaceholder-variables"},{title:".lib-background-gradient()",filename:"utilities",url:"utilities.html#libbackgroundgradient"},{title:".lib-background-gradient() variables",filename:"utilities",url:"utilities.html#libbackgroundgradient-variables"},{title:"variables",filename:"variables",url:"variables.html"},{title:"List of Global Variables",filename:"variables",url:"variables.html#list-of-global-variables"},{title:"Table with rows hover mixin variables",filename:"variables",url:"variables.html#table-with-rows-hover-mixin-variables"},{title:"docs",filename:"docs",url:"docs.html"},{title:"Documentation",filename:"docs",url:"docs.html#documentation"}];(function(){"use strict";var b=function(a,b){return Array.prototype.indexOf.call(a,b)!==-1},c=function(a,b){return Array.prototype.filter.call(a,b)},d=function(a,b){return Array.prototype.forEach.call(a,b)},e=document.getElementsByTagName("body")[0];e.addEventListener("click",function(a){var b=a.target;b.tagName.toLowerCase()==="svg"&&(b=b.parentNode);var c=!1;b.dataset.toggle!=null&&(a.preventDefault(),b.classList.contains("is-active")||(c=!0)),d(e.querySelectorAll("[data-toggle]"),function(a){a.classList.remove("is-active"),document.getElementById(a.dataset.toggle).hidden=!0}),c&&(b.classList.add("is-active"),document.getElementById(b.dataset.toggle).hidden=!1)}),function(){var f=e.getElementsByClassName("nav")[0];if(!f)return;var g=document.createElement("ul");g.className="nav-results",g.id="nav-search",g.hidden=!0,d(a,function(a){var b,c,d;b=document.createElement("li"),b._title=a.title.toLowerCase(),b.hidden=!0,b.appendChild(c=document.createElement("a")),c.href=a.url,c.innerHTML=a.title,c.appendChild(d=document.createElement("span")),d.innerHTML=a.filename,d.className="nav-results-filename",g.appendChild(b)}),f.appendChild(g);var h=g.children,i=function(a){d(h,function(a){a.hidden=!0});var b=this.value.toLowerCase(),e=[];b!==""&&(e=c(h,function(a){return a._title.indexOf(b)!==-1})),e.length>0?(d(e,function(a){a.hidden=!1}),g.hidden=!1):g.hidden=!0},j=f.querySelector('input[type="search"]');j.addEventListener("keyup",i),j.addEventListener("focus",i),e.addEventListener("click",function(a){if(a.target.classList&&a.target.classList.contains("search"))return;g.hidden=!0}),g.addEventListener("click",function(a){j.value=""});var k=document.createElement("ul");k.id="nav-toc",k.hidden=!0,k.className="nav-results toc-list",c(e.getElementsByTagName("*"),function(a){return b(["h1","h2","h3"],a.tagName.toLowerCase())}).map(function(a){var b=document.createElement("li"),c=document.createElement("a"),d=a.tagName.toLowerCase()[1];c.classList.add("level-"+d),b.appendChild(c),c.href="#"+a.id,c.innerHTML=a.innerHTML,k.appendChild(b)}),f.appendChild(k)}()})(),function(){"use strict";if(location.hash==="#__preview__"||location.protocol==="data:")return;var a=function(a,b){return Array.prototype.forEach.call(a,b)},b=function(a,b){var e=Array.prototype.slice.call(arguments,2);return d(a,function(a){return(c(b)?b||a:a[b]).apply(a,e)})},c=function(a){return Object.prototype.toString.call(a)==="[object Function]"},d=function(a,b){return Array.prototype.map.call(a,b)},e=function(a,b){return d(a,function(a){return a[b]})},f=function(a){var b={},c=a.split(";");for(var d=0;c.length>d;d++){var e=c[d].trim().split("=");b[e[0]]=e[1]}return b},g=function(a,c){return b(e(a,"classList"),"remove",c)},h=function(a,b){a.contentDocument.defaultView.postMessage(b,"*")},i=document.getElementsByTagName("head")[0],j=document.getElementsByTagName("body")[0],k=e(i.querySelectorAll('style[type="text/preview"]'),"innerHTML").join(""),l=e(i.querySelectorAll('script[type="text/preview"]'),"innerHTML").join(""),m=location.href.split("#")[0]+"#__preview__",n=document.createElement("iframe");n.src="data:text/html,",j.appendChild(n),n.addEventListener("load",function(){var b={sameOriginDataUri:!0};try{this.contentDocument,this.contentDocument||(b.sameOriginDataUri=!1)}catch(c){b.sameOriginDataUri=!1}this.parentNode.removeChild(this),a(j.getElementsByTagName("textarea"),function(a,c){o(a,b,c),q(),p(a)})});var o=function(a,b,c){var d,e,f;d=document.createElement("div"),d.appendChild(e=document.createElement("div")),d.className="preview",e.appendChild(f=document.createElement("iframe")),e.className="resizeable",f.setAttribute("scrolling","no"),f.name="iframe"+c++,f.addEventListener("load",function(){var c,d,e,f,g,i,j;j=this.contentDocument;if(!b.sameOriginDataUri&&this.src!==m)return;this.src===m&&(c=j.createElement("html"),c.appendChild(j.createElement("head")),c.appendChild(d=j.createElement("body")),d.innerHTML=a.textContent,j.replaceChild(c,j.documentElement)),g=j.createElement("head"),g.appendChild(f=j.createElement("style")),g.appendChild(e=j.createElement("script")),e.textContent=l,f.textContent=k,i=j.getElementsByTagName("head")[0],i.parentNode.replaceChild(g,i),h(this,"getHeight")});var g;b.sameOriginDataUri?g="data:text/html;charset=utf-8,"+encodeURIComponent("<!doctype html><html><head></head></body>"+a.textContent):g=m,f.setAttribute("src",g);var i=function(){f.contentDocument.body.innerHTML=this.value,h(f,"getHeight")};a.addEventListener("keypress",i),a.addEventListener("keyup",i),a.parentNode.insertBefore(d,a)},p=function(a){var b=document.createElement("div");b.className="preview-code",b.style.position="absolute",b.style.left="-9999px",j.appendChild(b);var c=parseInt(window.getComputedStyle(a).getPropertyValue("max-height"),10),d=function(a){b.textContent=this.value+"\n";var d=b.offsetHeight+2;d>=c?this.style.overflow="auto":this.style.overflow="hidden",this.style.height=b.offsetHeight+2+"px"};a.addEventListener("keypress",d),a.addEventListener("keyup",d),d.call(a)},q=function(){var b=j.getElementsByClassName("settings")[0],c=j.getElementsByClassName("resizeable"),d=30,e=function(b){document.cookie="preview-width="+b,a(c,function(a){b==="auto"&&(b=a.parentNode.offsetWidth),a.style.width=b+"px",h(a.getElementsByTagName("iframe")[0],"getHeight")})},i=f(document.cookie)["preview-width"];if(i){e(i),g(b.getElementsByClassName("is-active"),"is-active");var k=b.querySelector('button[data-width="'+i+'"]');k&&k.classList.add("is-active")}window.addEventListener("message",function(a){if(a.data==null||!a.source)return;var b=a.data,c=document.getElementsByName(a.source.name)[0];b.height!=null&&c&&(c.parentNode.style.height=b.height+d+"px")},!1),b&&c.length>0&&(b.hidden=!1,b.addEventListener("click",function(a){var c=a.target.tagName.toLowerCase(),d;if(c==="button")d=a.target;else{if(c!=="svg")return;d=a.target.parentNode}a.preventDefault(),g(b.getElementsByClassName("is-active"),"is-active"),d.classList.add("is-active");var f=d.dataset.width;e(f)}))}}()})()</script></body></html><!-- Generated with StyleDocco (http://jacobrask.github.com/styledocco). --> diff --git a/lib/web/css/source/components/_modals.less b/lib/web/css/source/components/_modals.less index 80b1c85ceb46e..fb29a74c60585 100644 --- a/lib/web/css/source/components/_modals.less +++ b/lib/web/css/source/components/_modals.less @@ -100,6 +100,12 @@ left: 0; overflow-y: auto; + &.confirm { + .modal-inner-wrap { + .lib-css(width, @modal-popup-confirm__width); + } + } + &._show { .modal-inner-wrap { -webkit-transform: translateY(0); @@ -192,18 +198,14 @@ &._inner-scroll { overflow-y: visible; - .ie11 &, - .ie10 &, - .ie9 & { + .ie11 & { overflow-y: auto; } .modal-inner-wrap { max-height: 90%; - .ie11 &, - .ie10 &, - .ie9 & { + .ie11 & { max-height: none; } } diff --git a/lib/web/css/source/lib/_navigation.less b/lib/web/css/source/lib/_navigation.less index b2ed4352a334a..acae3c629500e 100644 --- a/lib/web/css/source/lib/_navigation.less +++ b/lib/web/css/source/lib/_navigation.less @@ -92,6 +92,9 @@ .lib-css(padding, @_nav-level0-item-padding); .lib-css(text-transform, @_nav-level0-text-transform); word-wrap: break-word; + &:hover { + .lib-css(color, @navigation-level0-item__hover__color); + } } &.active { @@ -139,6 +142,11 @@ .submenu { > li { word-wrap: break-word; + > a { + &:hover { + .lib-css(color, @navigation-level0-item__hover__color); + } + } } &:not(:first-child) { @@ -178,6 +186,9 @@ .lib-css(text-decoration, @_submenu-item-text-decoration); display: block; line-height: normal; + &:hover { + .lib-css(color, @navigation-level0-item__hover__color); + } } } } diff --git a/lib/web/css/source/lib/_rating.less b/lib/web/css/source/lib/_rating.less index e585e4489d65e..535fa44616039 100644 --- a/lib/web/css/source/lib/_rating.less +++ b/lib/web/css/source/lib/_rating.less @@ -38,7 +38,7 @@ input[type="radio"] { .lib-visually-hidden(); - &:focus, + &:hover, &:checked { + label { &:before { diff --git a/lib/web/css/source/lib/_resets.less b/lib/web/css/source/lib/_resets.less index 36c7e6bf4ded3..8228a07ef92ab 100644 --- a/lib/web/css/source/lib/_resets.less +++ b/lib/web/css/source/lib/_resets.less @@ -185,7 +185,7 @@ mark { background: #ff0; - color: #000; + color: @color-black; } small { @@ -465,13 +465,13 @@ ins { background-color: #ff9; - color: #000; + color: @color-black; text-decoration: none; } mark { background-color: #ff9; - color: #000; + color: @color-black; font-style: italic; font-weight: bold; } diff --git a/lib/web/css/source/lib/_responsive.less b/lib/web/css/source/lib/_responsive.less index d1bbbae7f6875..efb706f85eac0 100644 --- a/lib/web/css/source/lib/_responsive.less +++ b/lib/web/css/source/lib/_responsive.less @@ -27,24 +27,24 @@ & when (@media-target = 'mobile'), (@media-target = 'all') { - @media only screen and (max-width: (@screen__xxs - 1)) { - .media-width('max', @screen__xxs); + @media only screen and (max-width: @screen__m) { + .media-width('max', (@screen__m + 1)); } - @media only screen and (max-width: (@screen__xs - 1)) { - .media-width('max', @screen__xs); + @media only screen and (max-width: (@screen__m - 1)) { + .media-width('max', @screen__m); } @media only screen and (max-width: (@screen__s - 1)) { .media-width('max', @screen__s); } - @media only screen and (max-width: (@screen__m - 1)) { - .media-width('max', @screen__m); + @media only screen and (max-width: (@screen__xs - 1)) { + .media-width('max', @screen__xs); } - @media only screen and (max-width: @screen__m) { - .media-width('max', (@screen__m + 1)); + @media only screen and (max-width: (@screen__xxs - 1)) { + .media-width('max', @screen__xxs); } @media all and (min-width: @screen__s) { diff --git a/lib/web/css/source/lib/_tables.less b/lib/web/css/source/lib/_tables.less index 9c37e17b4fe82..43b63152946f8 100644 --- a/lib/web/css/source/lib/_tables.less +++ b/lib/web/css/source/lib/_tables.less @@ -530,7 +530,7 @@ display: block; .lib-css(padding, @_table-responsive-cell-padding); - &:before { + &[data-th]:before { .lib-css(padding-right, @table-cell__padding-horizontal); content: attr(data-th)': '; display: inline-block; diff --git a/lib/web/css/source/lib/_utilities.less b/lib/web/css/source/lib/_utilities.less index 08a82494b3e5c..222eb4741e85e 100644 --- a/lib/web/css/source/lib/_utilities.less +++ b/lib/web/css/source/lib/_utilities.less @@ -260,9 +260,8 @@ @_line-height: normal ) { .lib-font-size(@_font-size); - font-size: @_font-size; + .lib-line-height(@_line-height); letter-spacing: normal; - line-height: @_line-height; } // diff --git a/lib/web/css/source/lib/variables/_navigation.less b/lib/web/css/source/lib/variables/_navigation.less index 8cffee7f8e71a..2a4aa11014dce 100644 --- a/lib/web/css/source/lib/variables/_navigation.less +++ b/lib/web/css/source/lib/variables/_navigation.less @@ -28,6 +28,7 @@ @navigation-level0-item__active__border-width: 0 0 0 8px; @navigation-level0-item__active__color: ''; @navigation-level0-item__active__text-decoration: ''; +@navigation-level0-item__hover__color: @primary__color; @submenu__background: ''; @submenu__border: ''; diff --git a/lib/web/extjs/ext-tree.js b/lib/web/extjs/ext-tree.js index 35b41260569c2..362e928c541c9 100644 --- a/lib/web/extjs/ext-tree.js +++ b/lib/web/extjs/ext-tree.js @@ -10,7 +10,7 @@ Ext={};window["undefined"]=window["undefined"];Ext.apply=function(o,c,_3){if(_3) -(function(){var _1;Ext.lib.Dom={getViewWidth:function(_2){return _2?this.getDocumentWidth():this.getViewportWidth();},getViewHeight:function(_3){return _3?this.getDocumentHeight():this.getViewportHeight();},getDocumentHeight:function(){var _4=(document.compatMode!="CSS1Compat")?document.body.scrollHeight:document.documentElement.scrollHeight;return Math.max(_4,this.getViewportHeight());},getDocumentWidth:function(){var _5=(document.compatMode!="CSS1Compat")?document.body.scrollWidth:document.documentElement.scrollWidth;return Math.max(_5,this.getViewportWidth());},getViewportHeight:function(){var _6=self.innerHeight;var _7=document.compatMode;if((_7||Ext.isIE)&&!Ext.isOpera){_6=(_7=="CSS1Compat")?document.documentElement.clientHeight:document.body.clientHeight;}return _6;},getViewportWidth:function(){var _8=self.innerWidth;var _9=document.compatMode;if(_9||Ext.isIE){_8=(_9=="CSS1Compat")?document.documentElement.clientWidth:document.body.clientWidth;}return _8;},isAncestor:function(p,c){p=Ext.getDom(p);c=Ext.getDom(c);if(!p||!c){return false;}if(p.contains&&!Ext.isSafari){return p.contains(c);}else{if(p.compareDocumentPosition){return !!(p.compareDocumentPosition(c)&16);}else{var _c=c.parentNode;while(_c){if(_c==p){return true;}else{if(!_c.tagName||_c.tagName.toUpperCase()=="HTML"){return false;}}_c=_c.parentNode;}return false;}}},getRegion:function(el){return Ext.lib.Region.getRegion(el);},getY:function(el){return this.getXY(el)[1];},getX:function(el){return this.getXY(el)[0];},getXY:function(el){var p,pe,b,_14,bd=document.body;el=Ext.getDom(el);if(el.getBoundingClientRect){b=el.getBoundingClientRect();_14=fly(document).getScroll();return [b.left+_14.left,b.top+_14.top];}else{var x=el.offsetLeft,y=el.offsetTop;p=el.offsetParent;var _18=false;if(p!=el){while(p){x+=p.offsetLeft;y+=p.offsetTop;if(Ext.isSafari&&!_18&&fly(p).getStyle("position")=="absolute"){_18=true;}if(Ext.isGecko){pe=fly(p);var bt=parseInt(pe.getStyle("borderTopWidth"),10)||0;var bl=parseInt(pe.getStyle("borderLeftWidth"),10)||0;x+=bl;y+=bt;if(p!=el&&pe.getStyle("overflow")!="visible"){x+=bl;y+=bt;}}p=p.offsetParent;}}if(Ext.isSafari&&(_18||fly(el).getStyle("position")=="absolute")){x-=bd.offsetLeft;y-=bd.offsetTop;}}p=el.parentNode;while(p&&p!=bd){if(!Ext.isOpera||(Ext.isOpera&&p.tagName!="TR"&&fly(p).getStyle("display")!="inline")){x-=p.scrollLeft;y-=p.scrollTop;}p=p.parentNode;}return [x,y];},setXY:function(el,xy){el=Ext.fly(el,"_setXY");el.position();var pts=el.translatePoints(xy);if(xy[0]!==false){el.dom.style.left=pts.left+"px";}if(xy[1]!==false){el.dom.style.top=pts.top+"px";}},setX:function(el,x){this.setXY(el,[x,false]);},setY:function(el,y){this.setXY(el,[false,y]);}};Ext.lib.Event={getPageX:function(e){return Event.pointerX(e.browserEvent||e);},getPageY:function(e){return Event.pointerY(e.browserEvent||e);},getXY:function(e){e=e.browserEvent||e;return [Event.pointerX(e),Event.pointerY(e)];},getTarget:function(e){return Event.element(e.browserEvent||e);},resolveTextNode:function(_26){if(_26&&3==_26.nodeType){return _26.parentNode;}else{return _26;}},getRelatedTarget:function(ev){ev=ev.browserEvent||ev;var t=ev.relatedTarget;if(!t){if(ev.type=="mouseout"){t=ev.toElement;}else{if(ev.type=="mouseover"){t=ev.fromElement;}}}return this.resolveTextNode(t);},on:function(el,_2a,fn){Event.observe(el,_2a,fn,false);},un:function(el,_2d,fn){Event.stopObserving(el,_2d,fn,false);},purgeElement:function(el){},preventDefault:function(e){e=e.browserEvent||e;if(e.preventDefault){e.preventDefault();}else{e.returnValue=false;}},stopPropagation:function(e){e=e.browserEvent||e;if(e.stopPropagation){e.stopPropagation();}else{e.cancelBubble=true;}},stopEvent:function(e){Event.stop(e.browserEvent||e);},onAvailable:function(el,fn,_35,_36){var _37=new Date(),iid;var f=function(){if(_37.getElapsed()>10000){clearInterval(iid);}var el=document.getElementById(id);if(el){clearInterval(iid);fn.call(_35||window,el);}};iid=setInterval(f,50);}};Ext.lib.Ajax=function(){var _3b=function(cb){return cb.success?function(xhr){cb.success.call(cb.scope||window,{responseText:xhr.responseText,responseXML:xhr.responseXML,argument:cb.argument});}:Ext.emptyFn;};var _3e=function(cb){return cb.failure?function(xhr){cb.failure.call(cb.scope||window,{responseText:xhr.responseText,responseXML:xhr.responseXML,argument:cb.argument});}:Ext.emptyFn;};return {request:function(_41,uri,cb,_44){new Ajax.Request(uri,{method:_41,parameters:_44||"",timeout:cb.timeout,onSuccess:_3b(cb),onFailure:_3e(cb)});},formRequest:function(_45,uri,cb,_48,_49,_4a){new Ajax.Request(uri,{method:Ext.getDom(_45).method||"POST",parameters:Form.serialize(_45)+(_48?"&"+_48:""),timeout:cb.timeout,onSuccess:_3b(cb),onFailure:_3e(cb)});},isCallInProgress:function(_4b){return false;},abort:function(_4c){return false;},serializeForm:function(_4d){return Form.serialize(_4d.dom||_4d,true);}};}();Ext.lib.Anim=function(){var _4e={easeOut:function(pos){return 1-Math.pow(1-pos,2);},easeIn:function(pos){return 1-Math.pow(1-pos,2);}};var _51=function(cb,_53){return {stop:function(_54){this.effect.cancel();},isAnimated:function(){return this.effect.state=="running";},proxyCallback:function(){Ext.callback(cb,_53);}};};return {scroll:function(el,_56,_57,_58,cb,_5a){var _5b=_51(cb,_5a);el=Ext.getDom(el);el.scrollLeft=_56.to[0];el.scrollTop=_56.to[1];_5b.proxyCallback();return _5b;},motion:function(el,_5d,_5e,_5f,cb,_61){return this.run(el,_5d,_5e,_5f,cb,_61);},color:function(el,_63,_64,_65,cb,_67){return this.run(el,_63,_64,_65,cb,_67);},run:function(el,_69,_6a,_6b,cb,_6d,_6e){var o={};for(var k in _69){switch(k){case "points":var by,pts,e=Ext.fly(el,"_animrun");e.position();if(by=_69.points.by){var xy=e.getXY();pts=e.translatePoints([xy[0]+by[0],xy[1]+by[1]]);}else{pts=e.translatePoints(_69.points.to);}o.left=pts.left+"px";o.top=pts.top+"px";break;case "width":o.width=_69.width.to+"px";break;case "height":o.height=_69.height.to+"px";break;case "opacity":o.opacity=String(_69.opacity.to);break;default:o[k]=String(_69[k].to);break;}}var _75=_51(cb,_6d);_75.effect=new Effect.Morph(Ext.id(el),{duration:_6a,afterFinish:_75.proxyCallback,transition:_4e[_6b]||Effect.Transitions.linear,style:o});return _75;}};}();function fly(el){if(!_1){_1=new Ext.Element.Flyweight();}_1.dom=el;return _1;}Ext.lib.Region=function(t,r,b,l){this.top=t;this[1]=t;this.right=r;this.bottom=b;this.left=l;this[0]=l;};Ext.lib.Region.prototype={contains:function(_7b){return (_7b.left>=this.left&&_7b.right<=this.right&&_7b.top>=this.top&&_7b.bottom<=this.bottom);},getArea:function(){return ((this.bottom-this.top)*(this.right-this.left));},intersect:function(_7c){var t=Math.max(this.top,_7c.top);var r=Math.min(this.right,_7c.right);var b=Math.min(this.bottom,_7c.bottom);var l=Math.max(this.left,_7c.left);if(b>=t&&r>=l){return new Ext.lib.Region(t,r,b,l);}else{return null;}},union:function(_81){var t=Math.min(this.top,_81.top);var r=Math.max(this.right,_81.right);var b=Math.max(this.bottom,_81.bottom);var l=Math.min(this.left,_81.left);return new Ext.lib.Region(t,r,b,l);},adjust:function(t,l,b,r){this.top+=t;this.left+=l;this.right+=r;this.bottom+=b;return this;}};Ext.lib.Region.getRegion=function(el){var p=Ext.lib.Dom.getXY(el);var t=p[1];var r=p[0]+el.offsetWidth;var b=p[1]+el.offsetHeight;var l=p[0];return new Ext.lib.Region(t,r,b,l);};Ext.lib.Point=function(x,y){if(x instanceof Array){y=x[1];x=x[0];}this.x=this.right=this.left=this[0]=x;this.y=this.top=this.bottom=this[1]=y;};Ext.lib.Point.prototype=new Ext.lib.Region();if(Ext.isIE){Event.observe(window,"unload",function(){var p=Function.prototype;delete p.createSequence;delete p.defer;delete p.createDelegate;delete p.createCallback;delete p.createInterceptor;});}})(); +(function(){var _1;Ext.lib.Dom={getViewWidth:function(_2){return _2?this.getDocumentWidth():this.getViewportWidth();},getViewHeight:function(_3){return _3?this.getDocumentHeight():this.getViewportHeight();},getDocumentHeight:function(){var _4=(document.compatMode!="CSS1Compat")?document.body.scrollHeight:document.documentElement.scrollHeight;return Math.max(_4,this.getViewportHeight());},getDocumentWidth:function(){var _5=(document.compatMode!="CSS1Compat")?document.body.scrollWidth:document.documentElement.scrollWidth;return Math.max(_5,this.getViewportWidth());},getViewportHeight:function(){var _6=self.innerHeight;var _7=document.compatMode;if((_7||Ext.isIE)&&!Ext.isOpera){_6=(_7=="CSS1Compat")?document.documentElement.clientHeight:document.body.clientHeight;}return _6;},getViewportWidth:function(){var _8=self.innerWidth;var _9=document.compatMode;if(_9||Ext.isIE){_8=(_9=="CSS1Compat")?document.documentElement.clientWidth:document.body.clientWidth;}return _8;},isAncestor:function(p,c){p=Ext.getDom(p);c=Ext.getDom(c);if(!p||!c){return false;}if(p.contains&&!Ext.isSafari){return p.contains(c);}else{if(p.compareDocumentPosition){return !!(p.compareDocumentPosition(c)&16);}else{var _c=c.parentNode;while(_c){if(_c==p){return true;}else{if(!_c.tagName||_c.tagName.toUpperCase()=="HTML"){return false;}}_c=_c.parentNode;}return false;}}},getRegion:function(el){return Ext.lib.Region.getRegion(el);},getY:function(el){return this.getXY(el)[1];},getX:function(el){return this.getXY(el)[0];},getXY:function(el){var p,pe,b,_14,bd=document.body;el=Ext.getDom(el);if(el.getBoundingClientRect){b=el.getBoundingClientRect();_14=fly(document).getScroll();return [b.left+_14.left,b.top+_14.top];}else{var x=el.offsetLeft,y=el.offsetTop;p=el.offsetParent;var _18=false;if(p!=el){while(p){x+=p.offsetLeft;y+=p.offsetTop;if(Ext.isSafari&&!_18&&fly(p).getStyle("position")=="absolute"){_18=true;}if(Ext.isGecko){pe=fly(p);var bt=parseInt(pe.getStyle("borderTopWidth"),10)||0;var bl=parseInt(pe.getStyle("borderLeftWidth"),10)||0;x+=bl;y+=bt;if(p!=el&&pe.getStyle("overflow")!="visible"){x+=bl;y+=bt;}}p=p.offsetParent;}}if(Ext.isSafari&&(_18||fly(el).getStyle("position")=="absolute")){x-=bd.offsetLeft;y-=bd.offsetTop;}}p=el.parentNode;while(p&&p!=bd){if(!Ext.isOpera||(Ext.isOpera&&p.tagName!="TR"&&fly(p).getStyle("display")!="inline")){x-=p.scrollLeft;y-=p.scrollTop;}p=p.parentNode;}return [x,y];},setXY:function(el,xy){el=Ext.fly(el,"_setXY");el.position();var pts=el.translatePoints(xy);if(xy[0]!==false){el.dom.style.left=pts.left+"px";}if(xy[1]!==false){el.dom.style.top=pts.top+"px";}},setX:function(el,x){this.setXY(el,[x,false]);},setY:function(el,y){this.setXY(el,[false,y]);}};Ext.lib.Event={getPageX:function(e){return Event.pointerX(e.browserEvent||e);},getPageY:function(e){return Event.pointerY(e.browserEvent||e);},getXY:function(e){e=e.browserEvent||e;return [Event.pointerX(e),Event.pointerY(e)];},getTarget:function(e){return Event.element(e.browserEvent||e);},resolveTextNode:function(_26){if(_26&&3==_26.nodeType){return _26.parentNode;}else{return _26;}},getRelatedTarget:function(ev){ev=ev.browserEvent||ev;var t=ev.relatedTarget;if(!t){if(ev.type=="mouseout"){t=ev.toElement;}else{if(ev.type=="mouseover"){t=ev.fromElement;}}}return this.resolveTextNode(t);},on:function(el,_2a,fn){Event.observe(el,_2a,fn,false);},un:function(el,_2d,fn){Event.stopObserving(el,_2d,fn,false);},purgeElement:function(el){},preventDefault:function(e){e=e.browserEvent||e;if(e.preventDefault){e.preventDefault();}else{e.returnValue=false;}},stopPropagation:function(e){e=e.browserEvent||e;if(e.stopPropagation){e.stopPropagation();}else{e.cancelBubble=true;}},stopEvent:function(e){Event.stop(e.browserEvent||e);},onAvailable:function(el,fn,_35,_36){var _37=new Date(),iid;var f=function(){if(_37.getElapsed()>10000){clearInterval(iid);}var el=document.getElementById(id);if(el){clearInterval(iid);fn.call(_35||window,el);}};iid=setInterval(f,50);}};Ext.lib.Ajax=function(){var _3b=function(cb){return cb.success?function(xhr){cb.success.call(cb.scope||window,{responseText:xhr.responseText,responseXML:xhr.responseXML,argument:cb.argument});}:Ext.emptyFn;};var _3e=function(cb){return cb.failure?function(xhr){cb.failure.call(cb.scope||window,{responseText:xhr.responseText,responseXML:xhr.responseXML,argument:cb.argument});}:Ext.emptyFn;};return {request:function(_41,uri,cb,_44){new Ajax.Request(uri,{method:_41,parameters:_44||"",timeout:cb.timeout,onSuccess:_3b(cb),onFailure:_3e(cb)});},formRequest:function(_45,uri,cb,_48,_49,_4a){new Ajax.Request(uri,{method:Ext.getDom(_45).method||"POST",parameters:Form.serialize(_45)+(_48?"&"+_48:""),timeout:cb.timeout,onSuccess:_3b(cb),onFailure:_3e(cb)});},isCallInProgress:function(_4b){return false;},abort:function(_4c){return false;},serializeForm:function(_4d){return Form.serialize(_4d.dom||_4d,true);}};}();Ext.lib.Anim=function(){var _4e={easeOut:function(pos){return 1-Math.pow(1-pos,2);},easeIn:function(pos){return 1-Math.pow(1-pos,2);}};var _51=function(cb,_53){return {stop:function(_54){this.effect.cancel();},isAnimated:function(){return this.effect.state=="running";},proxyCallback:function(){Ext.callback(cb,_53);}};};return {scroll:function(el,_56,_57,_58,cb,_5a){var _5b=_51(cb,_5a);el=Ext.getDom(el);el.scrollLeft=_56.scroll.to[0];el.scrollTop=_56.scroll.to[1];_5b.proxyCallback();return _5b;},motion:function(el,_5d,_5e,_5f,cb,_61){return this.run(el,_5d,_5e,_5f,cb,_61);},color:function(el,_63,_64,_65,cb,_67){return this.run(el,_63,_64,_65,cb,_67);},run:function(el,_69,_6a,_6b,cb,_6d,_6e){var o={};for(var k in _69){switch(k){case "points":var by,pts,e=Ext.fly(el,"_animrun");e.position();if(by=_69.points.by){var xy=e.getXY();pts=e.translatePoints([xy[0]+by[0],xy[1]+by[1]]);}else{pts=e.translatePoints(_69.points.to);}o.left=pts.left+"px";o.top=pts.top+"px";break;case "width":o.width=_69.width.to+"px";break;case "height":o.height=_69.height.to+"px";break;case "opacity":o.opacity=String(_69.opacity.to);break;default:o[k]=String(_69[k].to);break;}}var _75=_51(cb,_6d);_75.effect=new Effect.Morph(Ext.id(el),{duration:_6a,afterFinish:_75.proxyCallback,transition:_4e[_6b]||Effect.Transitions.linear,style:o});return _75;}};}();function fly(el){if(!_1){_1=new Ext.Element.Flyweight();}_1.dom=el;return _1;}Ext.lib.Region=function(t,r,b,l){this.top=t;this[1]=t;this.right=r;this.bottom=b;this.left=l;this[0]=l;};Ext.lib.Region.prototype={contains:function(_7b){return (_7b.left>=this.left&&_7b.right<=this.right&&_7b.top>=this.top&&_7b.bottom<=this.bottom);},getArea:function(){return ((this.bottom-this.top)*(this.right-this.left));},intersect:function(_7c){var t=Math.max(this.top,_7c.top);var r=Math.min(this.right,_7c.right);var b=Math.min(this.bottom,_7c.bottom);var l=Math.max(this.left,_7c.left);if(b>=t&&r>=l){return new Ext.lib.Region(t,r,b,l);}else{return null;}},union:function(_81){var t=Math.min(this.top,_81.top);var r=Math.max(this.right,_81.right);var b=Math.max(this.bottom,_81.bottom);var l=Math.min(this.left,_81.left);return new Ext.lib.Region(t,r,b,l);},adjust:function(t,l,b,r){this.top+=t;this.left+=l;this.right+=r;this.bottom+=b;return this;}};Ext.lib.Region.getRegion=function(el){var p=Ext.lib.Dom.getXY(el);var t=p[1];var r=p[0]+el.offsetWidth;var b=p[1]+el.offsetHeight;var l=p[0];return new Ext.lib.Region(t,r,b,l);};Ext.lib.Point=function(x,y){if(x instanceof Array){y=x[1];x=x[0];}this.x=this.right=this.left=this[0]=x;this.y=this.top=this.bottom=this[1]=y;};Ext.lib.Point.prototype=new Ext.lib.Region();if(Ext.isIE){Event.observe(window,"unload",function(){var p=Function.prototype;delete p.createSequence;delete p.defer;delete p.createDelegate;delete p.createCallback;delete p.createInterceptor;});}})(); diff --git a/lib/web/fotorama/fotorama.js b/lib/web/fotorama/fotorama.js index c86cd40198b2b..89cb315b0d2c4 100644 --- a/lib/web/fotorama/fotorama.js +++ b/lib/web/fotorama/fotorama.js @@ -831,7 +831,7 @@ fotoramaVersion = '4.6.4'; } type = 'youtube'; } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; } else if (href.host.match(/vimeo\.com/)) { diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 26d6679bb3ce5..09ceafc011d6f 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -434,7 +434,11 @@ define([ showLoader: true }).done($.proxy(function () { self.reload(); - self.element.find('#delete_files').toggleClass(self.options.hidden, true); + self.element.find('#delete_files, #insert_files').toggleClass(self.options.hidden, true); + + $(window).trigger('fileDeleted.mediabrowser', { + ids: ids + }); }, this)); }, diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index 487c71484e4c5..b363109d80695 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -494,7 +494,8 @@ define([ inputs.each(function (item) { // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic if ((!item.type || item.type != 'hidden') && !($(item.id + '_inherit') && $(item.id + '_inherit').checked) && //eslint-disable-line - !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) //eslint-disable-line + !(currentConfig['can_edit_price'] != undefined && !currentConfig['can_edit_price']) && //eslint-disable-line + !item.id.endsWith('_inherit') ) { item.disabled = false; jQuery(item).removeClass('ignore-validate'); diff --git a/lib/web/mage/adminhtml/wysiwyg/events.js b/lib/web/mage/adminhtml/wysiwyg/events.js new file mode 100644 index 0000000000000..36c680a7ffe46 --- /dev/null +++ b/lib/web/mage/adminhtml/wysiwyg/events.js @@ -0,0 +1,22 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([], function () { + 'use strict'; + + return { + afterInitialization: 'afterInitialization', + afterChangeContent: 'afterChangeContent', + afterUndo: 'afterUndo', + afterPaste: 'afterPaste', + beforeSetContent: 'beforeSetContent', + afterSetContent: 'afterSetContent', + afterSave: 'afterSave', + afterOpenFileBrowser: 'afterOpenFileBrowser', + afterFormSubmit: 'afterFormSubmit', + afterBlur: 'afterBlur', + afterFocus: 'afterFocus' + }; +}); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index 73fd8658854f2..18d71aad2071a 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -7,7 +7,8 @@ /* eslint-disable strict */ define([ 'wysiwygAdapter', - 'mage/adminhtml/events' + 'mage/adminhtml/events', + 'mage/adminhtml/wysiwyg/widget' ], function (wysiwyg, varienGlobalEvents) { return function (config) { tinymce.create('tinymce.plugins.magentowidget', { diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index d992947dca6e3..223c6d6ab04ea 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -8,12 +8,14 @@ define([ 'jquery', 'underscore', 'wysiwygAdapter', + 'module', 'mage/translate', 'prototype', 'mage/adminhtml/events', 'mage/adminhtml/browser' -], function (jQuery, _, wysiwygAdapter) { - var wysiwygSetup = Class.create({ +], function (jQuery, _, wysiwygAdapter, module) { + var baseConfig = module.config().config || {}, + wysiwygSetup = Class.create({ wysiwygInstance: null }); @@ -27,7 +29,10 @@ define([ var WysiwygInstancePrototype = new wysiwygAdapter.getAdapterPrototype(); _.bindAll(this, 'openFileBrowser'); + + config = Object.assign({}, baseConfig, config || {}); this.wysiwygInstance = new WysiwygInstancePrototype(htmlId, config); + this.wysiwygInstance.eventBus = this.eventBus = new window.varienEvents(); }, /** @@ -67,6 +72,9 @@ define([ updateContent: function (content) { return this.wysiwygInstance.encodeContent(content); } + }; window.wysiwygSetup = wysiwygSetup; + + return wysiwygSetup; }); diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/themes/ui.css b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/themes/ui.css index 274dcc124a3a6..27373b2a5147b 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/themes/ui.css +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/themes/ui.css @@ -15,6 +15,8 @@ font-style: normal; font-size: 14px; color: #000; + box-sizing: initial; + word-break: break-all; } .magento-placeholder-error { diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 8f20243383db8..46a09b28c4a7e 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -10,10 +10,11 @@ define([ 'underscore', 'tinymce4', 'mage/adminhtml/events', + 'mage/adminhtml/wysiwyg/events', 'mage/translate', 'prototype', 'jquery/ui' -], function (jQuery, _, tinyMCE4, varienGlobalEvents) { +], function (jQuery, _, tinyMCE4, varienGlobalEvents, wysiwygEvents) { 'use strict'; var tinyMce4Wysiwyg = Class.create(); @@ -72,10 +73,6 @@ define([ this.turnOff(); - if (typeof mode === 'undefined') { - mode = this.mode; - } - if (this.config.plugins) { this.config.plugins.forEach(function (plugin) { var deferred; @@ -105,11 +102,21 @@ define([ } settings = this.getSettings(); - settings.mode = mode; + + if (mode === 'inline') { + settings.inline = true; + + if (!isNaN(settings.toolbarZIndex)) { + tinyMCE4.ui.FloatPanel.zIndex = settings.toolbarZIndex; + } + + varienGlobalEvents.removeEventHandler('tinymceChange', this.onChangeContent); + } jQuery.when.apply(jQuery, deferreds).done(function () { tinyMCE4.init(settings); this.getPluginButtons().hide(); + this.eventBus.attachEventHandler('open_browser_callback', this.openFileBrowser); }.bind(this)); }, @@ -165,10 +172,11 @@ define([ * @return {Object} */ getSettings: function () { - var settings; + var settings, + eventBus = this.eventBus; settings = { - selector: 'textarea#' + this.getId(), + selector: '#' + this.getId(), theme: 'modern', 'entity_encoding': 'raw', 'convert_urls': false, @@ -187,22 +195,35 @@ define([ editor.on('BeforeSetContent', function (evt) { varienGlobalEvents.fireEvent('tinymceBeforeSetContent', evt); + eventBus.fireEvent(wysiwygEvents.beforeSetContent); }); editor.on('SaveContent', function (evt) { varienGlobalEvents.fireEvent('tinymceSaveContent', evt); + eventBus.fireEvent(wysiwygEvents.afterSave); }); editor.on('paste', function (evt) { varienGlobalEvents.fireEvent('tinymcePaste', evt); + eventBus.fireEvent(wysiwygEvents.afterPaste); }); editor.on('PostProcess', function (evt) { varienGlobalEvents.fireEvent('tinymceSaveContent', evt); + eventBus.fireEvent(wysiwygEvents.afterSave); }); editor.on('undo', function (evt) { varienGlobalEvents.fireEvent('tinymceUndo', evt); + eventBus.fireEvent(wysiwygEvents.afterUndo); + }); + + editor.on('focus', function () { + eventBus.fireEvent(wysiwygEvents.afterFocus); + }); + + editor.on('blur', function () { + eventBus.fireEvent(wysiwygEvents.afterBlur); }); /** @@ -210,6 +231,7 @@ define([ */ onChange = function (evt) { varienGlobalEvents.fireEvent('tinymceChange', evt); + eventBus.fireEvent(wysiwygEvents.afterChangeContent); }; editor.on('Change', onChange); @@ -221,6 +243,7 @@ define([ editor.on('init', function (args) { varienGlobalEvents.fireEvent('wysiwygEditorInitialized', args.target); + eventBus.fireEvent(wysiwygEvents.afterInitialization); }); } }; @@ -241,12 +264,15 @@ define([ * @param {*} w */ settings['file_browser_callback'] = function (fieldName, url, objectType, w) { - varienGlobalEvents.fireEvent('open_browser_callback', { + var payload = { win: w, type: objectType, field: fieldName - }); - }; + }; + + varienGlobalEvents.fireEvent('open_browser_callback', payload); + this.eventBus.fireEvent('open_browser_callback', payload); + }.bind(this); } if (this.config.width) { @@ -303,6 +329,15 @@ define([ this.activeEditor().execCommand('mceInsertContent', typeof ui !== 'undefined' ? ui : false, content); }, + /** + * Replace entire contents of wysiwyg with string content parameter + * + * @param {String} content + */ + setContent: function (content) { + this.get(this.getId()).execCommand('mceSetContent', false, content); + }, + /** * Set caret location in WYSIWYG editor. * @@ -491,7 +526,7 @@ define([ * @return {String} */ getContent: function (id) { - return id ? this.get(id).getContent() : this.activeEditor().getContent(); + return id ? this.get(id).getContent() : this.get(this.getId()).getContent(); }, /** @@ -587,7 +622,7 @@ define([ */ encodeDirectives: function (content) { // collect all HTML tags with attributes that contain directives - return content.gsub(/<([a-z0-9\-\_]+[^>]+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".*?)>/i, function (match) { + return content.gsub(/<([a-z0-9\-\_]+[^>]+?)([a-z0-9\-\_]+="[^"]*?\{\{.+?\}\}.*?".*?)>/i, function (match) { var attributesString = match[2], decodedDirectiveString; diff --git a/lib/web/mage/adminhtml/wysiwyg/widget.js b/lib/web/mage/adminhtml/wysiwyg/widget.js index d609b3f244f1e..68206fdec6201 100644 --- a/lib/web/mage/adminhtml/wysiwyg/widget.js +++ b/lib/web/mage/adminhtml/wysiwyg/widget.js @@ -275,7 +275,12 @@ define([ } if (e != undefined && e.id) { //eslint-disable-line eqeqeq - widgetCode = Base64.idDecode(e.id); + // attempt to Base64-decode id on selected node; exception is thrown if it is in fact not a widget node + try { + widgetCode = Base64.idDecode(e.id); + } catch (ex) { + return false; + } if (widgetCode.indexOf('{{widget') !== -1) { this.optionValues = new Hash({}); diff --git a/lib/web/mage/collapsible.js b/lib/web/mage/collapsible.js index 97ee3af506f86..245c4a38a6aa9 100644 --- a/lib/web/mage/collapsible.js +++ b/lib/web/mage/collapsible.js @@ -448,6 +448,7 @@ define([ if (this.options.animate) { this._animate(showProps); } else { + this._scrollToTopIfVisible(this.content.get(0).parentElement); this.content.show(); } this._open(); @@ -553,6 +554,27 @@ define([ }, 1); }); } + }, + + /** + * @param {HTMLElement} elem + * @private + */ + _scrollToTopIfVisible: function (elem) { + if (this._isElementOutOfViewport(elem)) { + elem.scrollIntoView(); + } + }, + + /** + * @param {HTMLElement} elem + * @private + * @return {Boolean} + */ + _isElementOutOfViewport: function (elem) { + var rect = elem.getBoundingClientRect(); + + return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight; } }); diff --git a/lib/web/mage/common.js b/lib/web/mage/common.js index a333277d9e3cd..4742c77ea8627 100644 --- a/lib/web/mage/common.js +++ b/lib/web/mage/common.js @@ -11,4 +11,24 @@ define([ /* Form with auto submit feature */ $('form[data-auto-submit="true"]').submit(); + + //Add form keys. + $(document).on( + 'submit', + 'form', + function (e) { + var formKeyElement, + form = $(e.target), + formKey = $('input[name="form_key"]').val(); + + if (formKey && !form.find('input[name="form_key"]').length) { + formKeyElement = document.createElement('input'); + formKeyElement.setAttribute('type', 'hidden'); + formKeyElement.setAttribute('name', 'form_key'); + formKeyElement.setAttribute('value', formKey); + formKeyElement.setAttribute('auto-added-form-key', '1'); + form.get(0).appendChild(formKeyElement); + } + } + ); }); diff --git a/lib/web/mage/gallery/gallery.less b/lib/web/mage/gallery/gallery.less index 24a5a2b4033d3..3c35a772253bc 100644 --- a/lib/web/mage/gallery/gallery.less +++ b/lib/web/mage/gallery/gallery.less @@ -7,7 +7,6 @@ @import '../../css/source/lib/_lib.less'; // Global lib @import '../../css/source/_theme.less'; // Theme overrides @import '../../css/source/_variables.less'; // Local theme variables -@import '../../css/source/lib/_responsive.less'; @import 'module/_mixins.less'; //Mixins in gallery @import 'module/_extends.less'; @import 'module/_focus.less'; @@ -401,10 +400,6 @@ .fotorama-abs-center(); height: @size-fotorama-block; width: @size-fotorama-block; - - .ie9 & { - margin: (-@size-fotorama-block/2) 0 0 (-@size-fotorama-block/2); - } } } @@ -736,24 +731,30 @@ text-align: center; top: 0; z-index: @z-index-10; + overflow: hidden; + + .magnifier-large { + width: auto; + height: auto; + max-height: none; + max-width: none; + border: none; + position: absolute; + z-index: @z-index-1; + } } .magnifier-loader-text { margin-top: 10px; } -.magnifier-large { - position: absolute; - width: 32%; - z-index: @z-index-1; -} - .magnifier-preview { bottom: 0; left: 58%; overflow: hidden; padding: 0; position: absolute; + z-index: 2; top: 215px; &:not(.hidden) { background-color: @color-white; @@ -868,10 +869,6 @@ .fotorama__thumb--icon { .fotorama-abs-center(); width: 100%; - - .ie9 & { - margin: (-@fotorama-thumb-arrow/2) 0 0 (-@fotorama-thumb-arrow/2); - } } } .fotorama__thumb__arr--left { diff --git a/lib/web/mage/ie-class-fixer.js b/lib/web/mage/ie-class-fixer.js index 6e89c0779f1a0..683090b1d1386 100644 --- a/lib/web/mage/ie-class-fixer.js +++ b/lib/web/mage/ie-class-fixer.js @@ -7,19 +7,12 @@ (function () { var userAgent = navigator.userAgent, // user agent identifier html = document.documentElement, // html tag - version = 9, // minimal supported version of IE gap = ''; // gap between classes if (html.className) { // check if neighbour class exist in html tag gap = ' '; } // end if - for (version; version <= 10; version++) { // loop from minimal to 10 version of IE - if (userAgent.indexOf('MSIE ' + version) > -1) { // match IE individual name - html.className += gap + 'ie' + version; - } // end if - } - if (userAgent.match(/Trident.*rv[ :]*11\./)) { // Special case for IE11 html.className += gap + 'ie11'; } // end if diff --git a/lib/web/mage/requirejs/resolver.js b/lib/web/mage/requirejs/resolver.js index 5088206dd31d9..27779aca325de 100644 --- a/lib/web/mage/requirejs/resolver.js +++ b/lib/web/mage/requirejs/resolver.js @@ -8,11 +8,11 @@ define([ ], function (_) { 'use strict'; - var context = require.s.contexts._, - execCb = context.execCb, - registry = context.registry, - callbacks = [], - retries = 10, + var context = require.s.contexts._, + execCb = context.execCb, + registry = context.registry, + callbacks = [], + retries = 10, updateDelay = 1, ready, update; @@ -28,13 +28,13 @@ define([ } /** - * Checks if provided module is rejected during load. + * Checks if provided module is registered after load. * * @param {Object} module - Module to be checked. * @return {Boolean} */ - function isRejected(module) { - return registry[module.id] && registry[module.id].error; + function isRegistered(module) { + return registry[module.id] && (registry[module.id].inited || registry[module.id].error); } /** @@ -48,7 +48,7 @@ define([ return false; } - return module.depCount > _.filter(module.depMaps, isRejected).length; + return module.depCount > _.filter(module.depMaps, isRegistered).length; } /** diff --git a/lib/web/mage/requirejs/static.js b/lib/web/mage/requirejs/static.js index 237aa0c6a8a63..898850cb2948b 100644 --- a/lib/web/mage/requirejs/static.js +++ b/lib/web/mage/requirejs/static.js @@ -13,7 +13,7 @@ define('buildTools', [ isEnabled: storage.getItem(storeName) === null, /** - * Removes base url from the the provided string + * Removes base url from the provided string * * @param {String} url - Url to be processed. * @param {Object} config - RequiereJs config object. diff --git a/lib/web/mage/utils/objects.js b/lib/web/mage/utils/objects.js index d0b77aa4320e3..bd0e496339ace 100644 --- a/lib/web/mage/utils/objects.js +++ b/lib/web/mage/utils/objects.js @@ -83,7 +83,7 @@ define([ * @private * * @param {Object} parent - Object from which to remove property. - * @param {Array} path - Splitted path to the propery. + * @param {Array} path - Splitted path to the property. */ function removeNested(parent, path) { var field = path.pop(); diff --git a/lib/web/mage/utils/template.js b/lib/web/mage/utils/template.js index e29908b7c4e9c..7c50226d6aa3a 100644 --- a/lib/web/mage/utils/template.js +++ b/lib/web/mage/utils/template.js @@ -2,12 +2,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +/* eslint-disable no-shadow */ + define([ 'jquery', 'underscore', 'mage/utils/objects', 'mage/utils/strings' -], function (jQuery, _, utils, stringUtils) { +], function ($, _, utils, stringUtils) { 'use strict'; var tmplSettings = _.templateSettings, @@ -176,7 +179,7 @@ define([ if (isTemplate(value)) { list[key] = render(value, tmpl, castString); - } else if (jQuery.isPlainObject(value) || Array.isArray(value)) { + } else if ($.isPlainObject(value) || Array.isArray(value)) { _.each(value, iterate); } }); diff --git a/lib/web/mage/utils/wrapper.js b/lib/web/mage/utils/wrapper.js index c90d1a026b147..9d4bb045b5722 100644 --- a/lib/web/mage/utils/wrapper.js +++ b/lib/web/mage/utils/wrapper.js @@ -45,7 +45,7 @@ define([ return { /** - * Wraps target function with a specified wrapper, which will recieve + * Wraps target function with a specified wrapper, which will receive * reference to the original function as a first argument. * * @param {Function} target - Function to be wrapped. diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 6258b3c627370..d08819ebe94aa 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -990,6 +990,12 @@ }, $.mage.__('Please use only letters (a-z or A-Z) or numbers (0-9) in this field. No spaces or other characters are allowed.') //eslint-disable-line max-len ], + 'validate-not-number-first': [ + function (value) { + return $.mage.isEmptyNoTrim(value) || /^[^0-9-\.].*$/.test(value.trim()); + }, + $.mage.__('First character must be letter.') + ], 'validate-date': [ function (value, params, additionalParams) { var test = moment(value, additionalParams.dateFormat); diff --git a/lib/web/mage/validation/url.js b/lib/web/mage/validation/url.js index 67e0e989d248e..00ac3fd4eb15d 100644 --- a/lib/web/mage/validation/url.js +++ b/lib/web/mage/validation/url.js @@ -42,7 +42,7 @@ define([], function () { /** * Sanitize url, replacing disallowed chars * - * @param {Sring} path - url to be normalized + * @param {String} path - url to be normalized * @returns {String} */ sanitize: function (path) { diff --git a/lib/web/magnifier/magnifier.js b/lib/web/magnifier/magnifier.js index c475364368922..150c8adf0b22b 100644 --- a/lib/web/magnifier/magnifier.js +++ b/lib/web/magnifier/magnifier.js @@ -9,101 +9,100 @@ var magnify = new Magnify($(this), options); - /*events must be tracked here*/ + /* events must be tracked here */ /** * Return that from _init function * */ return magnify; - }; function Magnify(element, options) { - var gOptions = options || {}, + var customUserOptions = options || {}, $box = $(element), $thumb, that = this, - largeWrapper = options.largeWrapper || '.magnifier-preview', - $largeWrapper = $(largeWrapper); + largeWrapper = options.largeWrapper || '.magnifier-preview', + $magnifierPreview = $(largeWrapper); curThumb = null, - currentOpts = { - x: 0, - y: 0, - w: 0, - h: 0, - lensW: 0, - lensH: 0, - lensBgX: 0, - lensBgY: 0, - largeW: 0, - largeH: 0, - largeL: 0, - largeT: 0, - zoom: 2, - zoomMin: 1.1, - zoomMax: 5, - mode: 'outside', - eventType: 'click', - status: 0, - zoomAttached: false, - zoomable: gOptions.zoomable !== undefined ? - gOptions.zoomable - : false, - onthumbenter: gOptions.onthumbenter !== undefined ? - gOptions.onthumbenter - : null, - onthumbmove: gOptions.onthumbmove !== undefined ? - gOptions.onthumbmove - : null, - onthumbleave: gOptions.onthumbleave !== undefined ? - gOptions.onthumbleave - : null, - onzoom: gOptions.onzoom !== undefined ? - gOptions.onzoom - : null - }, - pos = { - t: 0, - l: 0, - x: 0, - y: 0 - }, - gId = 0, - status = 0, - curIdx = '', - curLens = null, - curLarge = null, - lensbg = gOptions.bg !== undefined ? gOptions.lensbg : true, - gZoom = gOptions.zoom !== undefined ? - gOptions.zoom - : currentOpts.zoom, - gZoomMin = gOptions.zoomMin !== undefined ? - gOptions.zoomMin - : currentOpts.zoomMin, - gZoomMax = gOptions.zoomMax !== undefined ? - gOptions.zoomMax - : currentOpts.zoomMax, - gMode = gOptions.mode || currentOpts.mode, - gEventType = gOptions.eventType || currentOpts.eventType, - data = {}, - inBounds = false, - isOverThumb = false, - rate = 1, - paddingX = 0, - paddingY = 0, - enabled = true, - showWrapper = true; + magnifierOptions = { + x: 0, + y: 0, + w: 0, + h: 0, + lensW: 0, + lensH: 0, + lensBgX: 0, + lensBgY: 0, + largeW: 0, + largeH: 0, + largeL: 0, + largeT: 0, + zoom: 2, + zoomMin: 1.1, + zoomMax: 5, + mode: 'outside', + eventType: 'click', + status: 0, + zoomAttached: false, + zoomable: customUserOptions.zoomable !== undefined ? + customUserOptions.zoomable + : false, + onthumbenter: customUserOptions.onthumbenter !== undefined ? + customUserOptions.onthumbenter + : null, + onthumbmove: customUserOptions.onthumbmove !== undefined ? + customUserOptions.onthumbmove + : null, + onthumbleave: customUserOptions.onthumbleave !== undefined ? + customUserOptions.onthumbleave + : null, + onzoom: customUserOptions.onzoom !== undefined ? + customUserOptions.onzoom + : null + }, + pos = { + t: 0, + l: 0, + x: 0, + y: 0 + }, + gId = 0, + status = 0, + curIdx = '', + curLens = null, + curLarge = null, + lensbg = customUserOptions.bg !== undefined ? + customUserOptions.lensbg + : true, + gZoom = customUserOptions.zoom !== undefined ? + customUserOptions.zoom + : magnifierOptions.zoom, + gZoomMin = customUserOptions.zoomMin !== undefined ? + customUserOptions.zoomMin + : magnifierOptions.zoomMin, + gZoomMax = customUserOptions.zoomMax !== undefined ? + customUserOptions.zoomMax + : magnifierOptions.zoomMax, + gMode = customUserOptions.mode || magnifierOptions.mode, + gEventType = customUserOptions.eventType || magnifierOptions.eventType, + data = {}, + inBounds = false, + isOverThumb = false, + rate = 1, + paddingX = 0, + paddingY = 0, + enabled = true, + showWrapper = true; var MagnifyCls = { magnifyHidden: 'magnify-hidden', magnifyOpaque: 'magnify-opaque', magnifyFull: 'magnify-fullimage' - }; - /** * Update Lens positon on. * @@ -143,105 +142,104 @@ $(thumb).parent().append(lens); } - function updateLensOnLoad(idx, thumb, large, largeWrapper) { - var lens = $box.find('.magnify-lens'), + function updateLensOnLoad(idSelectorMainImg, thumb, largeImgInMagnifyLens, largeWrapper) { + var magnifyLensElement= $box.find('.magnify-lens'), textWrapper; - if (data[idx].status === 1) { + if (data[idSelectorMainImg].status === 1) { textWrapper = $('<div class="magnifier-loader-text"></div>'); - lens.className = 'magnifier-loader magnify-hidden'; + magnifyLensElement.className = 'magnifier-loader magnify-hidden'; textWrapper.html('Loading...'); - lens.html('').append(textWrapper); - } else if (data[idx].status === 2) { - lens.addClass(MagnifyCls.magnifyHidden); - lens.html(''); - large.id = idx + '-large'; - large.style.width = data[idx].largeW * rate + 'px'; - large.style.height = data[idx].largeH + 'px'; - large.className = 'magnifier-large magnify-hidden'; - - if (data[idx].mode === 'inside') { - lens.append(large); + magnifyLensElement.html('').append(textWrapper); + } else if (data[idSelectorMainImg].status === 2) { + magnifyLensElement.addClass(MagnifyCls.magnifyHidden); + magnifyLensElement.html(''); + + largeImgInMagnifyLens.id = idSelectorMainImg + '-large'; + largeImgInMagnifyLens.style.width = data[idSelectorMainImg].largeImgInMagnifyLensWidth + 'px'; + largeImgInMagnifyLens.style.height = data[idSelectorMainImg].largeImgInMagnifyLensHeight + 'px'; + largeImgInMagnifyLens.className = 'magnifier-large magnify-hidden'; + + if (data[idSelectorMainImg].mode === 'inside') { + magnifyLensElement.append(largeImgInMagnifyLens); } else { - largeWrapper.html('').append(large); + largeWrapper.html('').append(largeImgInMagnifyLens); } } - data[idx].lensH = data[idx].lensH > $thumb.height() ? $thumb.height() : data[idx].lensH; + data[idSelectorMainImg].lensH = data[idSelectorMainImg].lensH > $thumb.height() ? $thumb.height() : data[idSelectorMainImg].lensH; - if (Math.round(data[idx].lensW) === 0) { - lens.css('display', 'none'); + if (Math.round(data[idSelectorMainImg].lensW) === 0) { + magnifyLensElement.css('display', 'none'); } else { - lens.css({ - width: data[idx].lensW + 1 + 'px', - height: data[idx].lensH - 1 + 'px', + magnifyLensElement.css({ + width: Math.round(data[idSelectorMainImg].lensW) + 'px', + height: Math.round(data[idSelectorMainImg].lensH) + 'px', display: '' }); } } function getMousePos() { - var xPos = pos.x - currentOpts.x, - yPos = pos.y - currentOpts.y, + var xPos = pos.x - magnifierOptions.x, + yPos = pos.y - magnifierOptions.y, t, l; - inBounds = xPos < 0 || yPos < 0 || xPos > currentOpts.w || yPos > currentOpts.h ? false : true; + inBounds = xPos < 0 || yPos < 0 || xPos > magnifierOptions.w || yPos > magnifierOptions.h ? false : true; - l = xPos - currentOpts.lensW / 2; - t = yPos - currentOpts.lensH / 2; + l = xPos - magnifierOptions.lensW / 2; + t = yPos - magnifierOptions.lensH / 2; - if (currentOpts.mode !== 'inside') { - if (xPos < currentOpts.lensW / 2) { - l = 0; - } + if (xPos < magnifierOptions.lensW / 2) { + l = 0; + } - if (yPos < currentOpts.lensH / 2) { - t = 0; - } + if (yPos < magnifierOptions.lensH / 2) { + t = 0; + } - if (xPos - currentOpts.w + Math.ceil(currentOpts.lensW / 2) > 0) { - l = currentOpts.w - Math.ceil(currentOpts.lensW + 2); - } + if (xPos - magnifierOptions.w + Math.ceil(magnifierOptions.lensW / 2) > 0) { + l = magnifierOptions.w - Math.ceil(magnifierOptions.lensW + 2); + } - if (yPos - currentOpts.h + Math.ceil(currentOpts.lensH / 2) > 0) { - t = currentOpts.h - Math.ceil(currentOpts.lensH); - } + if (yPos - magnifierOptions.h + Math.ceil(magnifierOptions.lensH / 2) > 0) { + t = magnifierOptions.h - Math.ceil(magnifierOptions.lensH); + } - pos.l = l; - pos.t = t; + pos.l = l; + pos.t = t; - currentOpts.lensBgX = pos.l; - currentOpts.lensBgY = pos.t; + magnifierOptions.lensBgX = pos.l; + magnifierOptions.lensBgY = pos.t; - if (currentOpts.mode === 'inside') { - currentOpts.largeL = xPos * (currentOpts.zoom - currentOpts.lensW / currentOpts.w); - currentOpts.largeT = yPos * (currentOpts.zoom - currentOpts.lensH / currentOpts.h); - } else { - currentOpts.largeL = currentOpts.lensBgX * currentOpts.zoom * (currentOpts.largeWrapperW / currentOpts.w) * rate; - currentOpts.largeT = currentOpts.lensBgY * currentOpts.zoom * (currentOpts.largeWrapperH / currentOpts.h); - } + if (magnifierOptions.mode === 'inside') { + magnifierOptions.largeL = Math.round(xPos * (magnifierOptions.zoom - magnifierOptions.lensW / magnifierOptions.w)); + magnifierOptions.largeT = Math.round(yPos * (magnifierOptions.zoom - magnifierOptions.lensH / magnifierOptions.h)); + } else { + magnifierOptions.largeL = Math.round(magnifierOptions.lensBgX * magnifierOptions.zoom * (magnifierOptions.largeWrapperW / magnifierOptions.w)); + magnifierOptions.largeT = Math.round(magnifierOptions.lensBgY * magnifierOptions.zoom * (magnifierOptions.largeWrapperH / magnifierOptions.h)); } } function onThumbEnter() { if (_toBoolean(enabled)) { - currentOpts = data[curIdx]; + magnifierOptions = data[curIdx]; curLens = $box.find('.magnify-lens'); - if (currentOpts.status === 2) { + if (magnifierOptions.status === 2) { curLens.removeClass(MagnifyCls.magnifyOpaque); curLarge = $('#' + curIdx + '-large'); curLarge.removeClass(MagnifyCls.magnifyHidden); - } else if (currentOpts.status === 1) { + } else if (magnifierOptions.status === 1) { curLens.className = 'magnifier-loader'; } } } function onThumbLeave() { - if (currentOpts.status > 0) { - var handler = currentOpts.onthumbleave; + if (magnifierOptions.status > 0) { + var handler = magnifierOptions.onthumbleave; if (handler !== null) { handler({ @@ -266,28 +264,30 @@ function move() { if (_toBoolean(enabled)) { - if (status !== currentOpts.status) { + if (status !== magnifierOptions.status) { onThumbEnter(); } - if (currentOpts.status > 0) { - curThumb.className = currentOpts.thumbCssClass + ' magnify-opaque'; + if (magnifierOptions.status > 0) { + curThumb.className = magnifierOptions.thumbCssClass + ' magnify-opaque'; - if (currentOpts.status === 1) { + if (magnifierOptions.status === 1) { curLens.className = 'magnifier-loader'; - } else if (currentOpts.status === 2) { + } else if (magnifierOptions.status === 2) { curLens.removeClass(MagnifyCls.magnifyHidden); curLarge.removeClass(MagnifyCls.magnifyHidden); curLarge.css({ - left: '-' + currentOpts.largeL + 'px', - top: '-' + currentOpts.largeT + 'px' + left: '-' + magnifierOptions.largeL + 'px', + top: '-' + magnifierOptions.largeT + 'px' }); } - pos.t = pos.t <= 0 ? 0 : pos.t; + var borderOffset = 2; // Offset for magnify-lens border + pos.t = pos.t <= 0 ? 0 : pos.t - borderOffset; + curLens.css({ left: pos.l + paddingX + 'px', - top: pos.t + 1 + paddingY + 'px' + top: pos.t + paddingY + 'px' }); if (lensbg) { @@ -296,10 +296,10 @@ }); } else { curLens.get(0).style.backgroundPosition = '-' + - currentOpts.lensBgX + 'px -' + - currentOpts.lensBgY + 'px'; + magnifierOptions.lensBgX + 'px -' + + magnifierOptions.lensBgY + 'px'; } - var handler = currentOpts.onthumbmove; + var handler = magnifierOptions.onthumbmove; if (handler !== null) { handler({ @@ -312,33 +312,33 @@ } } - status = currentOpts.status; + status = magnifierOptions.status; } } - function setThumbData(thumb, thumbData) { - var thumbBounds = thumb.getBoundingClientRect(), + function setThumbData(mainImage, mainImageData) { + var thumbBounds = mainImage.getBoundingClientRect(), w = 0, h = 0; - thumbData.x = thumbBounds.left; - thumbData.y = thumbBounds.top; - thumbData.w = thumbBounds.right - thumbData.x; - thumbData.h = thumbBounds.bottom - thumbData.y; + mainImageData.x = Math.round(thumbBounds.left); + mainImageData.y = Math.round(thumbBounds.top); + mainImageData.w = Math.round(thumbBounds.right - mainImageData.x); + mainImageData.h = Math.round(thumbBounds.bottom - mainImageData.y); - if (thumbData.mode === 'inside') { - w = thumbData.w; - h = thumbData.h; + if (mainImageData.mode === 'inside') { + w = mainImageData.w; + h = mainImageData.h; } else { - w = thumbData.largeWrapperW; - h = thumbData.largeWrapperH; + w = mainImageData.largeWrapperW; + h = mainImageData.largeWrapperH; } - thumbData.largeW = thumbData.zoom * w; - thumbData.largeH = thumbData.zoom * h; + mainImageData.largeImgInMagnifyLensWidth = Math.round(mainImageData.zoom * w); + mainImageData.largeImgInMagnifyLensHeight = Math.round(mainImageData.zoom * h); - thumbData.lensW = thumbData.w / thumbData.zoom / rate; - thumbData.lensH = thumbData.h / thumbData.zoom; + mainImageData.lensW = Math.round(mainImageData.w / mainImageData.zoom); + mainImageData.lensH = Math.round(mainImageData.h / mainImageData.zoom); } function _init($box, options) { @@ -360,11 +360,11 @@ if (_toBoolean(enabled)) { - $largeWrapper.show().css('display', ''); - $largeWrapper.addClass(MagnifyCls.magnifyHidden); + $magnifierPreview.show().css('display', ''); + $magnifierPreview.addClass(MagnifyCls.magnifyHidden); set(opts); } else { - $largeWrapper.empty().hide(); + $magnifierPreview.empty().hide(); } } @@ -376,7 +376,7 @@ if (showWrapper) { - if (currentOpts.status !== 0) { + if (magnifierOptions.status !== 0) { onThumbLeave(); } handleEvents(e); @@ -390,7 +390,7 @@ if (showWrapper) { if (!isOverThumb) { - if (currentOpts.status !== 0) { + if (magnifierOptions.status !== 0) { onThumbLeave(); } handleEvents(e); @@ -420,7 +420,7 @@ onThumbEnter(src); - setThumbData(curThumb, currentOpts); + setThumbData(curThumb, magnifierOptions); pos.x = e.clientX; pos.y = e.clientY; @@ -428,7 +428,7 @@ getMousePos(); move(); - var handler = currentOpts.onthumbenter; + var handler = magnifierOptions.onthumbenter; if (handler !== null) { handler({ @@ -462,15 +462,15 @@ eventType = options.eventType || thumb.getAttribute('data-eventType') || gEventType, onthumbenter = options.onthumbenter !== undefined ? options.onthumbenter - : currentOpts.onthumbenter, + : magnifierOptions.onthumbenter, onthumbleave = options.onthumbleave !== undefined ? options.onthumbleave - : currentOpts.onthumbleave, + : magnifierOptions.onthumbleave, onthumbmove = options.onthumbmove !== undefined ? options.onthumbmove - : currentOpts.onthumbmove; + : magnifierOptions.onthumbmove; - largeUrl = $thumb.data('original') || gOptions.full || $thumb.attr('src'); + largeUrl = $thumb.data('original') || customUserOptions.full || $thumb.attr('src'); if (thumb.id === '') { idx = thumb.id = 'magnifier-item-' + gId; @@ -529,7 +529,6 @@ onthumbmove: onthumbmove }; - rate = $thumb.width() / $thumb.height() / (data[idx].largeWrapperW / data[idx].largeWrapperH); paddingX = ($thumb.parent().width() - $thumb.width()) / 2; paddingY = ($thumb.parent().height() - $thumb.height()) / 2; @@ -556,7 +555,6 @@ } function onMousemove(e) { - pos.x = e.clientX; pos.y = e.clientY; @@ -567,29 +565,30 @@ } if (inBounds && isOverThumb) { - $largeWrapper.removeClass(MagnifyCls.magnifyHidden); + if(gMode === 'outside'){ + $magnifierPreview.removeClass(MagnifyCls.magnifyHidden); + } move(); } else { onThumbLeave(); isOverThumb = false; - $largeWrapper.addClass(MagnifyCls.magnifyHidden); + $magnifierPreview.addClass(MagnifyCls.magnifyHidden); } } function onScroll() { if (curThumb !== null) { - setThumbData(curThumb, currentOpts); + setThumbData(curThumb, magnifierOptions); } } $(window).on('scroll', onScroll); $(window).resize(function () { - _init($box, gOptions); + _init($box, customUserOptions); }); $box.on('mousemove', onMousemove); - _init($box, gOptions); - + _init($box, customUserOptions); } }(jQuery)); diff --git a/lib/web/magnifier/magnify.js b/lib/web/magnifier/magnify.js index 34ba6e8e24135..1fb73ea28bff1 100644 --- a/lib/web/magnifier/magnify.js +++ b/lib/web/magnifier/magnify.js @@ -53,21 +53,14 @@ define([ /** * Return width and height of original image - * @param src path for original image + * @param img original image node * @returns {{rw: number, rh: number}} */ - function getImageSize(src) { - var img = new Image(), - imgSize = { - rw: 0, - rh: 0 - }; - - img.src = src; - imgSize.rw = img.width; - imgSize.rh = img.height; - - return imgSize; + function getImageSize(img) { + return { + rw: img.naturalWidth, + rh: img.naturalHeight + }; } /** @@ -192,7 +185,7 @@ define([ if (!e.data.$image || !e.data.$image.length) return; - imageSize = getImageSize($(fullscreenImageSelector)[0].src); + imageSize = getImageSize($(fullscreenImageSelector)[0]); parentWidth = e.data.$image.parent().width(); parentHeight = e.data.$image.parent().height(); isImageSmall = parentWidth >= imageSize.rw && parentHeight >= imageSize.rh; @@ -277,7 +270,6 @@ define([ settings = $.extend(dimentions, { top: top, - bottom: bottom, left: left, right: right }); @@ -332,7 +324,7 @@ define([ if (allowZoomIn && (!transitionEnabled || !transitionActive) && (isTouchEnabled || !$(zoomInButtonSelector).hasClass(zoomInDisabled))) { $image = $(fullscreenImageSelector); - imgOriginalSize = getImageSize($image[0].src); + imgOriginalSize = getImageSize($image[0]); imageWidth = $image.width(); imageHeight = $image.height(); ratio = imageWidth / imageHeight; @@ -557,6 +549,16 @@ define([ trailing: false }); + /** + * Returns top position value for passed jQuery object. + * + * @param $el + * @return {number} + */ + function getTop($el) { + return parseInt($el.get(0).style.top); + } + function shiftImage(dx, dy, e) { var top = +imagePosY + dy, left = +imagePosX + dx, @@ -584,16 +586,13 @@ define([ } if ($image.height() > $imageContainer.height()) { - - if ($imageContainer.offset().top + $imageContainer.height() > top + $image.height()) { - top = $imageContainer.offset().top + $imageContainer.height() - $image.height(); + if ($imageContainer.height() > $image.height() + top) { + $image.css('top', $imageContainer.height() - $image.height()); } else { - top = $imageContainer.offset().top < top ? 0 : top; + top = $image.height() - getTop($image) - $imageContainer.height(); + dy = dy < top ? dy : top; + $image.css('top', getTop($image) + dy); } - $image.offset({ - 'top': top - }); - $image.css('bottom', ''); } if ($image.width() > $imageContainer.width()) { @@ -624,7 +623,7 @@ define([ * @param e - event object */ function dblClickHandler(e) { - var imgOriginalSize = getImageSize($image[0].src), + var imgOriginalSize = getImageSize($image[0]), proportions; if (imgOriginalSize.rh < $image.parent().height() && imgOriginalSize.rw < $image.parent().width()) { @@ -690,7 +689,7 @@ define([ } else if (gallery.fullScreen && (!transitionEnabled || !transitionActive)) { e.preventDefault(); - imagePosY = $image.offset().top; + imagePosY = getTop($image); imagePosX = $image.offset().left; if (isTouchEnabled) { @@ -746,6 +745,7 @@ define([ } if (allowZoomOut) { + imagePosY = getTop($(fullscreenImageSelector, $gallery)); shiftImage(clientX - startX, clientY - startY, e); } } @@ -771,13 +771,13 @@ define([ isFullScreen = $(gallerySelector).data('fotorama').fullScreen, initVars = function () { imagePosX = $(fullscreenImageSelector, $gallery).offset().left; - imagePosY = $(fullscreenImageSelector, $gallery).offset().top; + imagePosY = getTop($(fullscreenImageSelector, $gallery)); }; if (($focus.attr('data-gallery-role') || !$focus.length) && allowZoomOut) { if (isFullScreen) { imagePosX = $(fullscreenImageSelector, $(gallerySelector)).offset().left; - imagePosY = $(fullscreenImageSelector, $(gallerySelector)).offset().top; + imagePosY = getTop($(fullscreenImageSelector, $(gallerySelector))); } if (e.keyCode === 39) { diff --git a/lib/web/modernizr/modernizr.js b/lib/web/modernizr/modernizr.js index 0833cfb105cee..8c826fa18c582 100644 --- a/lib/web/modernizr/modernizr.js +++ b/lib/web/modernizr/modernizr.js @@ -169,7 +169,7 @@ window.Modernizr = (function( window, document, undefined ) { // isEventSupported determines if a given element supports the given event // kangax.github.com/iseventsupported/ // - // The following results are known incorrects: + // The following results are known incorrect: // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 // ... @@ -1013,7 +1013,7 @@ window.Modernizr = (function( window, document, undefined ) { /** Name of the expando, to work with multiple documents or to re-shiv one document */ var expando = '_html5shiv'; - /** The id for the the documents expando */ + /** The id for the documents expando */ var expanID = 0; /** Cached data for each document */ diff --git a/lib/web/prototype/windows/README b/lib/web/prototype/windows/README index e2cb960834593..44da88b6adec5 100644 --- a/lib/web/prototype/windows/README +++ b/lib/web/prototype/windows/README @@ -133,7 +133,7 @@ See samples/index.html for more details and go on my web page : http://prototype - Add Windows.focusedWindow and Windows.closeAll - Add name to iframe in case of url window - Clean up code, use _ for private function (just name convention) - - Add Dialog.info function, usefull for for submit or notice info (in Rails) + - Add Dialog.info function, usefull for submit or notice info (in Rails) - Add minimize and maximize buttons - Add alert_lite.css without any images - Debug diff --git a/nginx.conf.sample b/nginx.conf.sample index 1e20a51a511d3..6f87a9a076666 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -3,7 +3,6 @@ # # use tcp connection # # server 127.0.0.1:9000; # # or socket -# server unix:/var/run/php5-fpm.sock; # server unix:/var/run/php/php7.0-fpm.sock; # } # server { @@ -103,7 +102,7 @@ location /static/ { rewrite ^/static/(version[^/]+/)?(.*)$ /static/$2 last; } - location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { + location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2|json)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; diff --git a/pub/static/.htaccess b/pub/static/.htaccess index a10e234e07ff2..f49dce25d433e 100644 --- a/pub/static/.htaccess +++ b/pub/static/.htaccess @@ -67,7 +67,7 @@ AddType application/xml xml <IfModule mod_headers.c> - <FilesMatch .*\.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$> + <FilesMatch .*\.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2|json)$> Header append Cache-Control public </FilesMatch> @@ -97,12 +97,13 @@ AddType application/xml xml ExpiresByType application/x-bzip2 "access plus 0 seconds" # CSS, JavaScript, html - <FilesMatch \.(css|js|html)$> + <FilesMatch \.(css|js|html|json)$> ExpiresDefault "access plus 1 year" </FilesMatch> ExpiresByType text/css "access plus 1 year" ExpiresByType text/html "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" + ExpiresByType application/json "access plus 1 year" # Favicon, images, flash <FilesMatch \.(ico|gif|png|jpg|jpeg|swf|svg)$> diff --git a/setup/performance-toolkit/README.md b/setup/performance-toolkit/README.md index 700f6cd0d775d..bb0a023ab8af1 100644 --- a/setup/performance-toolkit/README.md +++ b/setup/performance-toolkit/README.md @@ -116,7 +116,7 @@ To get more details about available JMeter options, read [Non-GUI Mode](http://j For example, you can run the B2C scenario via console with 90 threads for the Frontend Pool and 10 threads for the Admin Pool: cd {JMeter path}/bin/ - jmeter -n -t {path to peformance toolkit}/benchmark.jmx -j ./jmeter.log -l ./jmeter-results.jtl -Jhost=magento2.dev -Jbase_path=/ -Jadmin_path=admin -JfrontendPoolUsers=90 -JadminPoolUsers=10 + jmeter -n -t {path to performance toolkit}/benchmark.jmx -j ./jmeter.log -l ./jmeter-results.jtl -Jhost=magento2.dev -Jbase_path=/ -Jadmin_path=admin -JfrontendPoolUsers=90 -JadminPoolUsers=10 As a result, you will get `jmeter.log` and `jmeter-results.jtl`. The`jmeter.log` contains information about the test run and can be helpful in determining the cause of an error. The JTL file is a text file containing the results of a test run. It can be opened in the GUI mode to perform analysis of the results (see the *Output* section below). diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 7ee0be37f6cfb..1cab68878bc20 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -214,6 +214,11 @@ <stringProp name="Argument.value">${__P(admin_browse_product_filter_text,Product)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="admin_users_distribution_per_admin_pool" elementType="Argument"> + <stringProp name="Argument.name">admin_users_distribution_per_admin_pool</stringProp> + <stringProp name="Argument.value">${__P(admin_users_distribution_per_admin_pool,1)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="apiBasePercentage" elementType="Argument"> <stringProp name="Argument.name">apiBasePercentage</stringProp> <stringProp name="Argument.value">${__P(apiBasePercentage,0)}</stringProp> @@ -264,6 +269,11 @@ <stringProp name="Argument.value">${__P(browseProductGridPercentage,0)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="cache_hits_percentage" elementType="Argument"> + <stringProp name="Argument.name">cache_hits_percentage</stringProp> + <stringProp name="Argument.value">${__P(cache_hits_percentage,100)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="catalogGraphQLPercentage" elementType="Argument"> <stringProp name="Argument.name">catalogGraphQLPercentage</stringProp> <stringProp name="Argument.value">${__P(catalogGraphQLPercentage,0)}</stringProp> @@ -291,7 +301,7 @@ </elementProp> <elementProp name="configurable_products_count" elementType="Argument"> <stringProp name="Argument.name">configurable_products_count</stringProp> - <stringProp name="Argument.value">${__P(configurable_products_count,30)}</stringProp> + <stringProp name="Argument.value">${__P(configurable_products_count,15)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> <elementProp name="csrPoolUsers" elementType="Argument"> @@ -564,8 +574,8 @@ </elementProp> <stringProp name="HTTPSampler.domain">${host}</stringProp> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding">utf-8</stringProp> <stringProp name="HTTPSampler.path"/> @@ -643,7 +653,9 @@ props.remove("category_url_keys_list"); props.remove("category_name"); props.remove("category_names_list"); props.remove("simple_products_list"); +props.remove("simple_products_list_for_edit"); props.remove("configurable_products_list"); +props.remove("configurable_products_list_for_edit"); props.remove("users"); props.remove("customer_emails_list"); @@ -692,8 +704,8 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> @@ -772,8 +784,8 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -841,8 +853,8 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -933,8 +945,8 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/categories/list</stringProp> @@ -1044,6 +1056,186 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree/> </hashTree> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract categories id of last level" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_categories_id_of_last_level.jmx</stringProp> +</TestFragmentController> + <hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Clear Admin Category Management properties" enabled="true"> + <stringProp name="BeanShellSampler.query">props.remove("admin_category_ids_list");</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Get categories of last level" enabled="true"/> + <hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> + <stringProp name="VAR">admin_token</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="484395188">^[a-z0-9-]+$</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">1</intProp> + <stringProp name="Assertion.scope">variable</stringProp> + <stringProp name="Scope.variable">admin_token</stringProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Authorization</stringProp> + <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - API Get categories" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">children_count</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">0</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">level</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">2</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][0][conditionType]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">gt</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][conditionType]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${adminCategoryCount}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories/list</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">category_list_id</stringProp> + <stringProp name="RegexExtractor.regex">\{\"id\":(\d+),</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + </hashTree> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Category Id" enabled="true"> + <stringProp name="ForeachController.inputVal">category_list_id</stringProp> + <stringProp name="ForeachController.returnVal">category_id</stringProp> + <boolProp name="ForeachController.useSeparator">true</boolProp> + </ForeachController> + <hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="Process categories ids" enabled="true"> + <stringProp name="BeanShellSampler.query">import java.util.ArrayList; + +adminCategoryIdsList = props.get("admin_category_ids_list"); +// If it is first iteration of cycle then recreate categories ids list +if (adminCategoryIdsList == null) { + adminCategoryIdsList = new ArrayList(); + props.put("admin_category_ids_list", adminCategoryIdsList); +} +adminCategoryIdsList.add(vars.get("category_id"));</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + </hashTree> + </hashTree> + </hashTree> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract configurable products" enabled="true"> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_configurable_products.jmx</stringProp> </TestFragmentController> @@ -1076,8 +1268,420 @@ props.put("category_name", vars.get("category_name"));</stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> + <stringProp name="VAR">admin_token</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="484395188">^[a-z0-9-]+$</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">1</intProp> + <stringProp name="Assertion.scope">variable</stringProp> + <stringProp name="Scope.variable">admin_token</stringProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Authorization</stringProp> + <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get configurable products" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">type_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">configurable</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${configurable_products_count}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product url keys" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_products_url_keys</stringProp> + <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product ids" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_ids</stringProp> + <stringProp name="RegexExtractor.regex">\"id\":(\d+),</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product titles" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_names</stringProp> + <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product skus" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_skus</stringProp> + <stringProp name="RegexExtractor.regex">sku\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + </hashTree> + </hashTree> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare configurable products" enabled="true"> + <stringProp name="ForeachController.inputVal">configurable_product_ids</stringProp> + <stringProp name="ForeachController.returnVal">configurable_product_id</stringProp> + <boolProp name="ForeachController.useSeparator">true</boolProp> + </ForeachController> + <hashTree> + <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> + <stringProp name="CounterConfig.start">1</stringProp> + <stringProp name="CounterConfig.end"/> + <stringProp name="CounterConfig.incr">1</stringProp> + <stringProp name="CounterConfig.name">configurable_products_counter</stringProp> + <stringProp name="CounterConfig.format"/> + <boolProp name="CounterConfig.per_user">false</boolProp> + </CounterConfig> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect configurable product" enabled="true"> + <stringProp name="BeanShellSampler.query">import java.util.ArrayList; +import java.util.HashMap; +import org.apache.commons.codec.binary.Base64; + +// If it is first iteration of cycle then recreate productList +if (1 == Integer.parseInt(vars.get("configurable_products_counter"))) { + productList = new ArrayList(); + props.put("configurable_products_list", productList); +} else { + productList = props.get("configurable_products_list"); +} + +String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("configurable_products_url_keys_" + vars.get("configurable_products_counter"))+ vars.get("url_suffix"); +encodedUrl = Base64.encodeBase64(productUrl.getBytes()); +// Create product map +Map productMap = new HashMap(); +productMap.put("id", vars.get("configurable_product_id")); +productMap.put("title", vars.get("configurable_product_names_" + vars.get("configurable_products_counter"))); +productMap.put("sku", vars.get("configurable_product_skus_" + vars.get("configurable_products_counter"))); +productMap.put("url_key", vars.get("configurable_products_url_keys_" + vars.get("configurable_products_counter"))); +productMap.put("uenc", new String(encodedUrl)); + +// Collect products map in products list +productList.add(productMap);</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + </hashTree> + </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract configurable products for edit" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_configurable_products_for_edit.jmx</stringProp> +</TestFragmentController> + <hashTree> + <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve configurable products for edit" enabled="true"/> + <hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> + <stringProp name="VAR">admin_token</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="484395188">^[a-z0-9-]+$</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">1</intProp> + <stringProp name="Assertion.scope">variable</stringProp> + <stringProp name="Scope.variable">admin_token</stringProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Authorization</stringProp> + <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get configurable products for edit" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">type_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">configurable</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${configurable_products_count}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + </elementProp> + <elementProp name="searchCriteria[currentPage]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">2</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[currentPage]</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product url keys" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_products_for_edit_url_keys</stringProp> + <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product ids" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_for_edit_ids</stringProp> + <stringProp name="RegexExtractor.regex">\"id\":(\d+),</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product titles" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_for_edit_names</stringProp> + <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product skus" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">configurable_product_for_edit_skus</stringProp> + <stringProp name="RegexExtractor.regex">sku\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + </hashTree> + </hashTree> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare configurable products for edit" enabled="true"> + <stringProp name="ForeachController.inputVal">configurable_product_for_edit_ids</stringProp> + <stringProp name="ForeachController.returnVal">configurable_product_for_edit_id</stringProp> + <boolProp name="ForeachController.useSeparator">true</boolProp> + </ForeachController> + <hashTree> + <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter for edit" enabled="true"> + <stringProp name="CounterConfig.start">1</stringProp> + <stringProp name="CounterConfig.end"/> + <stringProp name="CounterConfig.incr">1</stringProp> + <stringProp name="CounterConfig.name">configurable_products_counter_for_edit</stringProp> + <stringProp name="CounterConfig.format"/> + <boolProp name="CounterConfig.per_user">false</boolProp> + </CounterConfig> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect configurable product for edit" enabled="true"> + <stringProp name="BeanShellSampler.query">import java.util.ArrayList; +import java.util.HashMap; +import org.apache.commons.codec.binary.Base64; + +if (1 == Integer.parseInt(vars.get("configurable_products_counter_for_edit"))) { + editProductList = new ArrayList(); + props.put("configurable_products_list_for_edit", editProductList); +} else { + productList = props.get("configurable_products_list_for_edit"); +} + +String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("configurable_products_for_edit_url_keys_" + vars.get("configurable_products_counter_for_edit"))+ vars.get("url_suffix"); +encodedUrl = Base64.encodeBase64(productUrl.getBytes()); +// Create product map +Map editProductMap = new HashMap(); +editProductMap.put("id", vars.get("configurable_product_for_edit_id")); +editProductMap.put("title", vars.get("configurable_product_for_edit_names_" + vars.get("configurable_products_counter_for_edit"))); +editProductMap.put("sku", vars.get("configurable_product_for_edit_skus_" + vars.get("configurable_products_counter_for_edit"))); +editProductMap.put("url_key", vars.get("configurable_products_for_edit_url_keys_" + vars.get("configurable_products_counter_for_edit"))); +editProductMap.put("uenc", new String(encodedUrl)); + +// Collect products map in products list +editProductList.add(editProductMap);</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + </hashTree> + </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract simple products for edit" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_simple_products_for_edit.jmx</stringProp> +</TestFragmentController> + <hashTree> + <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve simple products for edit" enabled="true"/> + <hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + </HeaderManager> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -1119,7 +1723,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> </collectionProp> </HeaderManager> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get configurable products" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get simple products for edit" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> @@ -1131,24 +1735,45 @@ props.put("category_name", vars.get("category_name"));</stringProp> </elementProp> <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">configurable</stringProp> + <stringProp name="Argument.value">simple</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> </elementProp> <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${configurable_products_count}</stringProp> + <stringProp name="Argument.value">${simple_products_count}</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> </elementProp> + <elementProp name="searchCriteria[currentPage]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[currentPage]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][1][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">attribute_set_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][1][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][1][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">4</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][1][value]</stringProp> + </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> @@ -1163,7 +1788,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree> <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product url keys" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">configurable_products_url_keys</stringProp> + <stringProp name="RegexExtractor.refname">simple_products_for_edit_url_keys</stringProp> <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> @@ -1172,7 +1797,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree/> <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product ids" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">configurable_product_ids</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_for_edit_ids</stringProp> <stringProp name="RegexExtractor.regex">\"id\":(\d+),</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> @@ -1181,7 +1806,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree/> <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product titles" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">configurable_product_names</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_for_edit_names</stringProp> <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> @@ -1190,7 +1815,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree/> <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product skus" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">configurable_product_skus</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_for_edit_skus</stringProp> <stringProp name="RegexExtractor.regex">sku\":\"(.*?)\"</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> @@ -1199,46 +1824,45 @@ props.put("category_name", vars.get("category_name"));</stringProp> <hashTree/> </hashTree> </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare configurable products" enabled="true"> - <stringProp name="ForeachController.inputVal">configurable_product_ids</stringProp> - <stringProp name="ForeachController.returnVal">configurable_product_id</stringProp> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare simple products for edit" enabled="true"> + <stringProp name="ForeachController.inputVal">simple_product_for_edit_ids</stringProp> + <stringProp name="ForeachController.returnVal">simple_product_for_edit_id</stringProp> <boolProp name="ForeachController.useSeparator">true</boolProp> </ForeachController> <hashTree> - <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> + <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter for edit" enabled="true"> <stringProp name="CounterConfig.start">1</stringProp> <stringProp name="CounterConfig.end"/> <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">configurable_products_counter</stringProp> + <stringProp name="CounterConfig.name">simple_products_counter_for_edit</stringProp> <stringProp name="CounterConfig.format"/> <boolProp name="CounterConfig.per_user">false</boolProp> </CounterConfig> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect configurable product" enabled="true"> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect simple product for edit" enabled="true"> <stringProp name="BeanShellSampler.query">import java.util.ArrayList; import java.util.HashMap; import org.apache.commons.codec.binary.Base64; -// If it is first iteration of cycle then recreate productList -if (1 == Integer.parseInt(vars.get("configurable_products_counter"))) { - productList = new ArrayList(); - props.put("configurable_products_list", productList); +if (1 == Integer.parseInt(vars.get("simple_products_counter_for_edit"))) { + editProductList = new ArrayList(); + props.put("simple_products_list_for_edit", editProductList); } else { - productList = props.get("configurable_products_list"); + productList = props.get("simple_products_counter_for_edit"); } -String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("configurable_products_url_keys_" + vars.get("configurable_products_counter"))+ vars.get("url_suffix"); +String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("simple_products_for_edit_url_keys_" + vars.get("simple_products_counter_for_edit"))+ vars.get("url_suffix"); encodedUrl = Base64.encodeBase64(productUrl.getBytes()); // Create product map -Map productMap = new HashMap(); -productMap.put("id", vars.get("configurable_product_id")); -productMap.put("title", vars.get("configurable_product_names_" + vars.get("configurable_products_counter"))); -productMap.put("sku", vars.get("configurable_product_skus_" + vars.get("configurable_products_counter"))); -productMap.put("url_key", vars.get("configurable_products_url_keys_" + vars.get("configurable_products_counter"))); -productMap.put("uenc", new String(encodedUrl)); +Map editProductMap = new HashMap(); +editProductMap.put("id", vars.get("simple_product_for_edit_id")); +editProductMap.put("title", vars.get("simple_product_for_edit_names_" + vars.get("simple_products_counter_for_edit"))); +editProductMap.put("sku", vars.get("simple_product_for_edit_skus_" + vars.get("simple_products_counter_for_edit"))); +editProductMap.put("url_key", vars.get("simple_products_for_edit_url_keys_" + vars.get("simple_products_counter_for_edit"))); +editProductMap.put("uenc", new String(encodedUrl)); // Collect products map in products list -productList.add(productMap);</stringProp> +editProductList.add(editProductMap);</stringProp> <stringProp name="BeanShellSampler.filename"/> <stringProp name="BeanShellSampler.parameters"/> <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> @@ -1279,8 +1903,8 @@ productList.add(productMap);</stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -1346,12 +1970,26 @@ productList.add(productMap);</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][1][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">attribute_set_id</stringProp> + <stringProp name="Argument.metadata">!=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][1][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">4</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][value]</stringProp> + </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> @@ -1467,8 +2105,8 @@ productList.add(productMap);</stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/user/roleGrid/limit/200/?ajax=true&isAjax=true</stringProp> @@ -1500,6 +2138,7 @@ productList.add(productMap);</stringProp> adminUserList.poll(); props.put("adminUserList", adminUserList); + props.put("adminUserListIterator", adminUserList.descendingIterator()); </stringProp> </BeanShellPostProcessor> <hashTree/> @@ -1516,8 +2155,8 @@ productList.add(productMap);</stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index/</stringProp> @@ -1590,6 +2229,13 @@ manager.add(cookie); </stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">filters[placeholder]</stringProp> </elementProp> + <elementProp name="filters[group_id]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">filters[group_id]</stringProp> + </elementProp> <elementProp name="filters[website_id]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">1</stringProp> @@ -1636,8 +2282,8 @@ manager.add(cookie); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -1751,6 +2397,59 @@ idsList.add(vars.get("customer_id"));</stringProp> </hashTree> </hashTree> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract region ids" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_region_ids.jmx</stringProp> + </TestFragmentController> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Extract Region ids" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="parent" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">US</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">parent</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/directory/json/countryRegion/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="Parse and put region id into variables" enabled="true"> + <stringProp name="scriptLanguage">groovy</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">import groovy.json.JsonSlurper +def jsonSlurper = new JsonSlurper(); +def regionResponse = jsonSlurper.parseText(prev.getResponseDataAsString()); + +regionResponse.each { region -> + if (region.label.toString() == "Alabama") { + props.put("alabama_region_id", region.value.toString()); + } else if (region.label.toString() == 'California') { + props.put("california_region_id", region.value.toString()); + } +}</stringProp> + </JSR223PostProcessor> + <hashTree/> + </hashTree> + </hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Validate properties and count users" enabled="true"> <stringProp name="BeanShellSampler.query">Boolean stopTestOnError (String error) { log.error(error); @@ -1762,9 +2461,15 @@ idsList.add(vars.get("customer_id"));</stringProp> if (props.get("simple_products_list") == null) { return stopTestOnError("Cannot find simple products. Test stopped."); } +if (props.get("simple_products_list_for_edit") == null) { + return stopTestOnError("Cannot find simple products for edit. Test stopped."); +} if (props.get("configurable_products_list") == null) { return stopTestOnError("Cannot find configurable products. Test stopped."); } +if (props.get("configurable_products_list_for_edit") == null) { + return stopTestOnError("Cannot find configurable products for edit. Test stopped."); +} if (props.get("customer_emails_list") == null) { return stopTestOnError("Cannot find customer emails. Test stopped."); } @@ -1816,8 +2521,8 @@ if (props.get("category_names_list") == null) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add</stringProp> @@ -1873,8 +2578,8 @@ if (props.get("category_names_list") == null) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}page_cache/block/render/</stringProp> @@ -1916,6 +2621,30 @@ if (props.get("category_names_list") == null) { <stringProp name="ThreadGroup.delay"/> <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/thread_group.jmx</stringProp></ThreadGroup> <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Cache hit miss" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); + +if ( + cacheHitPercent < 100 && + sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + doCache(); +} + +function doCache(){ + var random = Math.random() * 100; + if (cacheHitPercent < random) { + sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); + } +} +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Catalog Browsing By Customer" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> @@ -2038,8 +2767,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -2098,8 +2827,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -2170,8 +2899,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -2191,8 +2920,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -2222,8 +2951,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -2310,8 +3039,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -2378,8 +3107,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -2410,8 +3139,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -2544,8 +3273,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -2575,8 +3304,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -2663,8 +3392,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -2731,8 +3460,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -2802,6 +3531,30 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/search/search_terms.jmx</stringProp></CSVDataSet> <hashTree/> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Cache hit miss" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); + +if ( + cacheHitPercent < 100 && + sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + doCache(); +} + +function doCache(){ + var random = Math.random() * 100; + if (cacheHitPercent < random) { + sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); + } +} +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Quick Search" enabled="true"> <intProp name="ThroughputController.style">1</intProp> <boolProp name="ThroughputController.perThread">false</boolProp> @@ -2864,8 +3617,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -2903,8 +3656,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/result/</stringProp> @@ -2966,8 +3719,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/searchTermsLog/save/</stringProp> @@ -3045,8 +3798,8 @@ vars.put("product_url_key", product); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -3135,8 +3888,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -3174,8 +3927,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/result/</stringProp> @@ -3264,8 +4017,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/searchTermsLog/save/</stringProp> @@ -3309,8 +4062,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${attribute_1_filter_url}</stringProp> @@ -3381,8 +4134,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${attribute_2_filter_url}</stringProp> @@ -3469,8 +4222,8 @@ vars.put("product_url_key", product); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -3559,8 +4312,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -3591,8 +4344,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/advanced/</stringProp> @@ -3702,8 +4455,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalogsearch/advanced/result/</stringProp> @@ -3788,8 +4541,8 @@ vars.put("product_url_key", product); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -3925,8 +4678,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -3956,8 +4709,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -4058,8 +4811,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -4118,8 +4871,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -4171,8 +4924,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -4281,8 +5034,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -4337,8 +5090,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -4386,8 +5139,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/configurable-products/${product_sku}/options/all</stringProp> @@ -4454,8 +5207,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -4537,8 +5290,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -4699,8 +5452,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -4759,8 +5512,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -4831,8 +5584,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -4888,8 +5641,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -4942,8 +5695,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}wishlist/index/add/</stringProp> @@ -5004,8 +5757,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -5073,8 +5826,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}wishlist/index/remove/</stringProp> @@ -5094,8 +5847,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -5239,8 +5992,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -5340,8 +6093,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -5393,8 +6146,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalog/product_compare/add/</stringProp> @@ -5436,8 +6189,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -5518,8 +6271,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -5571,8 +6324,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalog/product_compare/add/</stringProp> @@ -5614,8 +6367,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -5646,8 +6399,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalog/product_compare/index/</stringProp> @@ -5682,8 +6435,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}catalog/product_compare/clear</stringProp> @@ -5806,8 +6559,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -5837,8 +6590,8 @@ vars.put("category_name", props.get("category_names_list").get(number)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -5939,8 +6692,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -5999,8 +6752,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -6052,8 +6805,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -6162,8 +6915,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -6218,8 +6971,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -6267,8 +7020,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/configurable-products/${product_sku}/options/all</stringProp> @@ -6335,8 +7088,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -6418,8 +7171,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -6476,14 +7229,26 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/simple_controller.jmx</stringProp> </GenericController> <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Get region data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script"> + vars.put("alabama_region_id", props.get("alabama_region_id")); + vars.put("california_region_id", props.get("california_region_id")); +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/get_region_data.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Checkout start" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/</stringProp> @@ -6549,8 +7314,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers/isEmailAvailable</stringProp> @@ -6608,8 +7373,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/estimate-shipping-methods</stringProp> @@ -6660,15 +7425,15 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"countryId":"US","regionId":"12","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> + <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"countryId":"US","regionId":"${california_region_id}","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/shipping-information</stringProp> @@ -6719,15 +7484,15 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"cartId":"${cart_id}","email":"test@example.com","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"countryId":"US","regionId":"12","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"}}</stringProp> + <stringProp name="Argument.value">{"cartId":"${cart_id}","email":"test@example.com","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"countryId":"US","regionId":"${california_region_id}","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/payment-information</stringProp> @@ -6797,8 +7562,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/onepage/success/</stringProp> @@ -6959,8 +7724,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -6990,8 +7755,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -7050,8 +7815,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -7122,8 +7887,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -7143,8 +7908,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -7245,8 +8010,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -7305,8 +8070,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -7358,8 +8123,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -7468,8 +8233,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -7524,8 +8289,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -7573,8 +8338,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/configurable-products/${product_sku}/options/all</stringProp> @@ -7641,8 +8406,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -7724,8 +8489,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -7782,14 +8547,26 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/simple_controller.jmx</stringProp> </GenericController> <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Get region data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script"> + vars.put("alabama_region_id", props.get("alabama_region_id")); + vars.put("california_region_id", props.get("california_region_id")); +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/get_region_data.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Checkout start" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/</stringProp> @@ -7895,8 +8672,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts/mine/estimate-shipping-methods-by-address-id</stringProp> @@ -7947,15 +8724,15 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"customerAddressId":"${address_id}","countryId":"US","regionId":5,"regionCode":"AR","region":"Arkansas","customerId":"${customer_id}","street":["123 Freedom Blvd. #123"],"telephone":"022-333-4455","postcode":"123123","city":"Fayetteville","firstname":"Anthony","lastname":"Nealy"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> + <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"customerAddressId":"${address_id}","countryId":"US","regionId":"${alabama_region_id}","regionCode":"AL","region":"Alabama","customerId":"${customer_id}","street":["123 Freedom Blvd. #123"],"telephone":"022-333-4455","postcode":"123123","city":"Fayetteville","firstname":"Anthony","lastname":"Nealy"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts/mine/shipping-information</stringProp> @@ -8006,15 +8783,15 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"cartId":"${cart_id}","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"customerAddressId":"${address_id}","countryId":"US","regionId":5,"regionCode":"AR","region":"Arkansas","customerId":"${customer_id}","street":["123 Freedom Blvd. #123"],"telephone":"022-333-4455","postcode":"123123","city":"Fayetteville","firstname":"Anthony","lastname":"Nealy"}}</stringProp> + <stringProp name="Argument.value">{"cartId":"${cart_id}","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"customerAddressId":"${address_id}","countryId":"US","regionId":"${alabama_region_id}","regionCode":"AL","region":"Alabama","customerId":"${customer_id}","street":["123 Freedom Blvd. #123"],"telephone":"022-333-4455","postcode":"123123","city":"Fayetteville","firstname":"Anthony","lastname":"Nealy"}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts/mine/payment-information</stringProp> @@ -8065,8 +8842,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/onepage/success/</stringProp> @@ -8098,8 +8875,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -8255,8 +9032,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -8315,8 +9092,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -8387,8 +9164,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -8444,8 +9221,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -8518,8 +9295,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}review/product/post/id/${product_id}</stringProp> @@ -8571,8 +9348,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -8600,8 +9377,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -8771,8 +9548,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -8831,8 +9608,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -8903,8 +9680,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -8924,8 +9701,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -8955,8 +9732,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${category_url_key}${url_suffix}</stringProp> @@ -9057,8 +9834,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -9117,8 +9894,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -9170,8 +9947,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -9280,8 +10057,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${product_url_key}${url_suffix}</stringProp> @@ -9336,8 +10113,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -9385,8 +10162,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/configurable-products/${product_sku}/options/all</stringProp> @@ -9453,8 +10230,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add/</stringProp> @@ -9536,8 +10313,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -9600,8 +10377,8 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/</stringProp> @@ -9690,8 +10467,8 @@ vars.put("item_id", vars.get("cart_items_qty_inputs_" + id)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol"/> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}checkout/cart/delete/</stringProp> @@ -9733,8 +10510,8 @@ vars.put("item_id", vars.get("cart_items_qty_inputs_" + id)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -9765,8 +10542,8 @@ vars.put("item_id", vars.get("cart_items_qty_inputs_" + id)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -9891,8 +10668,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}</stringProp> @@ -9922,8 +10699,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/login/</stringProp> @@ -9982,8 +10759,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/loginPost/</stringProp> @@ -10054,8 +10831,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/section/load/</stringProp> @@ -10075,8 +10852,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}sales/order/history/</stringProp> @@ -10121,8 +10898,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}sales/order/view/order_id/${orderId}</stringProp> @@ -10166,8 +10943,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}sales/order/shipment/order_id/${orderId}</stringProp> @@ -10205,8 +10982,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${popupLink}</stringProp> @@ -10238,8 +11015,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}downloadable/customer/products</stringProp> @@ -10293,8 +11070,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}sales/order/view/order_id/${orderId}</stringProp> @@ -10324,8 +11101,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}downloadable/download/link/id/${linkId}</stringProp> @@ -10346,8 +11123,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}wishlist</stringProp> @@ -10402,8 +11179,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}wishlist/index/share/wishlist_id/${wishlistId}/</stringProp> @@ -10456,8 +11233,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}wishlist/index/send/wishlist_id/${wishlistId}/</stringProp> @@ -10488,8 +11265,8 @@ vars.put("customer_email", customerUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}customer/account/logout/</stringProp> @@ -10641,7 +11418,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -10664,11 +11453,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -10745,8 +11534,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -10787,8 +11576,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/cms/page/</stringProp> @@ -10807,8 +11596,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/cms/page/new</stringProp> @@ -10940,8 +11729,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/cms/page/save/</stringProp> @@ -10979,8 +11768,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -10999,8 +11788,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -11106,7 +11898,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -11129,11 +11933,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -11210,8 +12014,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -11334,8 +12138,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -11451,8 +12255,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -11579,8 +12383,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -11706,8 +12510,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -11741,8 +12545,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -11761,8 +12565,11 @@ vars.put("grid_pages_count_filtered", pageCount); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -11868,7 +12675,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -11891,11 +12710,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -11972,8 +12791,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -12096,8 +12915,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -12213,8 +13032,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -12341,8 +13160,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -12468,8 +13287,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -12503,8 +13322,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -12523,8 +13342,11 @@ vars.put("grid_pages_count_filtered", pageCount); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -12630,7 +13452,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -12653,11 +13487,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -12734,8 +13568,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -12813,8 +13647,8 @@ vars.put("related_product_id", props.get("simple_products_list").get(relatedInde </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -12893,8 +13727,8 @@ vars.put("related_product_id", props.get("simple_products_list").get(relatedInde </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attributes</stringProp> @@ -12961,8 +13795,8 @@ vars.putObject("product_attributes", attributes); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product_set/index/filter/${attribute_set_filter}</stringProp> @@ -13010,15 +13844,15 @@ Random random = new Random(); if (${seedForRandom} > 0) { random.setSeed(${seedForRandom}); } -number = random.nextInt(props.get("simple_products_list").size()); -simpleList = props.get("simple_products_list").get(number); +number = random.nextInt(props.get("simple_products_list_for_edit").size()); +simpleList = props.get("simple_products_list_for_edit").get(number); vars.put("simple_product_1_id", simpleList.get("id")); vars.put("simple_product_1_name", simpleList.get("title")); do { - number1 = random.nextInt(props.get("simple_products_list").size()); + number1 = random.nextInt(props.get("simple_products_list_for_edit").size()); } while(number == number1); -simpleList = props.get("simple_products_list").get(number1); +simpleList = props.get("simple_products_list_for_edit").get(number1); vars.put("simple_product_2_id", simpleList.get("id")); vars.put("simple_product_2_name", simpleList.get("title")); @@ -13056,8 +13890,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/</stringProp> @@ -13086,8 +13920,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/new/set/4/type/bundle/</stringProp> @@ -13989,8 +14823,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/set/4/</stringProp> @@ -14923,8 +15757,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/set/4/type/bundle/back/edit/active_tab/product-details/</stringProp> @@ -14964,8 +15798,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/</stringProp> @@ -14995,8 +15829,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/new/set/${attribute_set_id}/type/configurable/</stringProp> @@ -15525,8 +16359,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/set/${attribute_set_id}/</stringProp> @@ -15693,7 +16527,7 @@ function addConfigurableMatrix(attributes) { </elementProp> <elementProp name="product[category_ids][0]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">4</stringProp> + <stringProp name="Argument.value">2</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">product[category_ids][0]</stringProp> @@ -16137,8 +16971,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/set/${attribute_set_id}/type/configurable/back/edit/active_tab/product-details/</stringProp> @@ -16273,8 +17107,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/</stringProp> @@ -16303,8 +17137,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/new/set/4/type/downloadable/</stringProp> @@ -16351,8 +17185,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/downloadable_file/upload/type/links/?isAjax=true</stringProp> @@ -16398,8 +17232,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/downloadable_file/upload/type/samples/?isAjax=true</stringProp> @@ -17131,8 +17965,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/set/4/type/downloadable/</stringProp> @@ -17853,8 +18687,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/set/4/type/downloadable/back/edit/active_tab/product-details/</stringProp> @@ -17898,8 +18732,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/</stringProp> @@ -17928,8 +18762,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/new/set/4/type/simple/</stringProp> @@ -18657,8 +19491,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/set/4/</stringProp> @@ -19368,8 +20202,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/set/4/type/simple/back/edit/active_tab/product-details/</stringProp> @@ -19410,8 +20244,8 @@ function addConfigurableMatrix(attributes) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -19430,8 +20264,11 @@ function addConfigurableMatrix(attributes) { <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -19537,7 +20374,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -19560,11 +20409,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -19641,8 +20490,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -19686,8 +20535,8 @@ vars.put("admin_user", adminUser); if (${seedForRandom} > 0) { random.setSeed(${seedForRandom} + ${__threadNum}); } - simpleCount = props.get("simple_products_list").size(); - configCount = props.get("configurable_products_list").size(); + simpleCount = props.get("simple_products_list_for_edit").size(); + configCount = props.get("configurable_products_list_for_edit").size(); productCount = 0; if (simpleCount > configCount) { productCount = configCount; @@ -19723,14 +20572,14 @@ vars.put("admin_user", adminUser); i = productClusterLength * currentThreadNum + iterator; //ids of simple and configurable products to edit - vars.put("simple_product_id", props.get("simple_products_list").get(i).get("id")); - vars.put("configurable_product_id", props.get("configurable_products_list").get(i).get("id")); + vars.put("simple_product_id", props.get("simple_products_list_for_edit").get(i).get("id")); + vars.put("configurable_product_id", props.get("configurable_products_list_for_edit").get(i).get("id")); //id of related product do { - relatedIndex = random.nextInt(props.get("simple_products_list").size()); + relatedIndex = random.nextInt(props.get("simple_products_list_for_edit").size()); } while(i == relatedIndex); - vars.put("related_product_id", props.get("simple_products_list").get(relatedIndex).get("id")); + vars.put("related_product_id", props.get("simple_products_list_for_edit").get(relatedIndex).get("id")); } catch (Exception ex) { log.info("Script execution failed", ex); }</stringProp> @@ -19745,8 +20594,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/edit/id/${simple_product_id}/</stringProp> @@ -19799,20 +20648,32 @@ vars.put("admin_user", adminUser); <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set updated values" enabled="true"> <stringProp name="TestPlan.comments">Passing arguments between threads</stringProp> <stringProp name="BeanShellSampler.query">//Additional category to be added + import java.util.Random; - int categoryId = Integer.parseInt(vars.get("simple_product_category_id")); - if (categoryId > 4) { - categoryId = categoryId - 1; - } else { - categoryId = categoryId + 1; - } - vars.put("category_additional", categoryId.toString()); - //New price - vars.put("price_new", "9999"); - //New special price - vars.put("special_price_new", "8888"); - //New quantity - vars.put("quantity_new", "100600");</stringProp> + Random randomGenerator = new Random(); + if (${seedForRandom} > 0) { + randomGenerator.setSeed(${seedForRandom} + ${__threadNum}); + } + + int categoryId = Integer.parseInt(vars.get("simple_product_category_id")); + categoryList = props.get("admin_category_ids_list"); + + if (categoryList.size() > 1) { + do { + int index = randomGenerator.nextInt(categoryList.size()); + newCategoryId = categoryList.get(index); + } while (categoryId == newCategoryId); + + vars.put("category_additional", newCategoryId.toString()); + } + + //New price + vars.put("price_new", "9999"); + //New special price + vars.put("special_price_new", "8888"); + //New quantity + vars.put("quantity_new", "100600"); + </stringProp> <stringProp name="BeanShellSampler.filename"/> <stringProp name="BeanShellSampler.parameters"/> <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> @@ -20257,8 +21118,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/id/${simple_product_id}/?isAjax=true</stringProp> @@ -20727,8 +21588,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/id/${simple_product_id}/back/edit/active_tab/product-details/</stringProp> @@ -20757,8 +21618,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/edit/id/${configurable_product_id}/</stringProp> @@ -21335,8 +22196,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/id/${configurable_product_id}/</stringProp> @@ -21807,8 +22668,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/id/${configurable_product_id}/back/edit/active_tab/product-details/</stringProp> @@ -21878,8 +22739,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -21898,8 +22759,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -22022,7 +22886,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -22045,11 +22921,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -22126,8 +23002,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -22164,8 +23040,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order/</stringProp> @@ -22275,8 +23151,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -22392,8 +23268,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -22494,8 +23370,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order/view/order_id/${order_id}/</stringProp> @@ -22540,8 +23416,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_invoice/start/order_id/${order_id}/</stringProp> @@ -22611,8 +23487,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_invoice/save/order_id/${order_id}/</stringProp> @@ -22642,8 +23518,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_creditmemo/start/order_id/${order_id}/</stringProp> @@ -22731,8 +23607,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_creditmemo/save/order_id/${order_id}/</stringProp> @@ -22771,8 +23647,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -22791,8 +23667,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -22898,7 +23777,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -22921,11 +23812,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -23002,8 +23893,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -23126,8 +24017,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -23243,8 +24134,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -23371,8 +24262,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -23498,8 +24389,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -23533,8 +24424,8 @@ vars.put("grid_pages_count_filtered", pageCount); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -23553,8 +24444,11 @@ vars.put("grid_pages_count_filtered", pageCount); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -23660,7 +24554,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -23683,11 +24589,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -23764,8 +24670,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -23796,6 +24702,18 @@ vars.put("admin_user", adminUser); <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/simple_controller.jmx</stringProp> </GenericController> <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Get region data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script"> + vars.put("alabama_region_id", props.get("alabama_region_id")); + vars.put("california_region_id", props.get("california_region_id")); +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/get_region_data.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Admin Create Order" enabled="true"/> <hashTree> <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Arguments" enabled="true"> @@ -23860,8 +24778,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_create/start/</stringProp> @@ -23903,8 +24821,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -23952,8 +24870,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/configurable-products/${configurable_product_1_sku}/options/all</stringProp> @@ -24075,8 +24993,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_create/loadBlock/block/search,items,shipping_method,totals,giftmessage,billing_method?isAjax=true</stringProp> @@ -24175,8 +25093,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_create/loadBlock/block/shipping_method,totals?isAjax=true</stringProp> @@ -24465,7 +25383,7 @@ catch (java.lang.Exception e) { <elementProp name="order[billing_address][region_id]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.name">order[billing_address][region_id]</stringProp> - <stringProp name="Argument.value">5</stringProp> + <stringProp name="Argument.value">${alabama_region_id}</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> </elementProp> @@ -24543,8 +25461,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_create/save/</stringProp> @@ -24690,8 +25608,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_invoice/save/order_id/${order_id}/</stringProp> @@ -24757,8 +25675,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/order_shipment/save/order_id/${order_id}/</stringProp> @@ -24791,8 +25709,8 @@ catch (java.lang.Exception e) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -24811,8 +25729,11 @@ catch (java.lang.Exception e) { <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -24895,8 +25816,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -24992,8 +25913,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers</stringProp> @@ -25033,8 +25954,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers/${customer_id}</stringProp> @@ -25096,8 +26017,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories</stringProp> @@ -25148,8 +26069,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories/${search_category_id}</stringProp> @@ -25198,8 +26119,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products</stringProp> @@ -25300,8 +26221,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/search</stringProp> @@ -25376,6 +26297,18 @@ if (testLabel </BeanShellSampler> <hashTree/> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Get region data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script"> + vars.put("alabama_region_id", props.get("alabama_region_id")); + vars.put("california_region_id", props.get("california_region_id")); +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/get_region_data.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> <stringProp name="BeanShellSampler.query"> @@ -25420,8 +26353,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts</stringProp> @@ -25475,8 +26408,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts/${quote_id}/items</stringProp> @@ -25506,8 +26439,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/carts/${quote_id}/items</stringProp> @@ -25545,8 +26478,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/</stringProp> @@ -25600,8 +26533,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/items</stringProp> @@ -25642,8 +26575,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/gift-message</stringProp> @@ -25679,8 +26612,8 @@ vars.put("product_sku", product.get("sku")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/estimate-shipping-methods</stringProp> @@ -25731,15 +26664,15 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"countryId":"US","regionId":"12","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> + <stringProp name="Argument.value">{"addressInformation":{"shipping_address":{"countryId":"US","regionId":"${california_region_id}","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname"},"shipping_method_code":"flatrate","shipping_carrier_code":"flatrate"}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/shipping-information</stringProp> @@ -25790,15 +26723,15 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"cartId":"${cart_id}","email":"test@example.com","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"countryId":"US","regionId":"12","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname","save_in_address_book":0}}</stringProp> + <stringProp name="Argument.value">{"cartId":"${cart_id}","email":"test@example.com","paymentMethod":{"method":"checkmo","po_number":null,"additional_data":null},"billingAddress":{"countryId":"US","regionId":"${california_region_id}","regionCode":"CA","region":"California","street":["10441 Jefferson Blvd ste 200"],"company":"","telephone":"3109450345","fax":"","postcode":"90232","city":"Culver City","firstname":"Name","lastname":"Lastname","save_in_address_book":0}}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/guest-carts/${cart_id}/payment-information</stringProp> @@ -25914,8 +26847,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products</stringProp> @@ -26006,8 +26939,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/${simple_product_sku}/stockItems/${simple_stock_item_id}</stringProp> @@ -26043,8 +26976,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/${simple_product_sku}</stringProp> @@ -26146,8 +27079,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products</stringProp> @@ -26232,8 +27165,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/${simple_product_sku}</stringProp> @@ -26399,7 +27332,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -26422,11 +27367,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -26503,8 +27448,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -26613,8 +27558,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -26775,8 +27720,8 @@ vars.put("visibility", String.valueOf(randomVisibility)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -26853,8 +27798,8 @@ vars.put("visibility", String.valueOf(randomVisibility)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product_action_attribute/edit</stringProp> @@ -26947,8 +27892,8 @@ vars.put("visibility", String.valueOf(randomVisibility)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product_action_attribute/validate</stringProp> @@ -27062,8 +28007,8 @@ vars.put("visibility", String.valueOf(randomVisibility)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product_action_attribute/save/store/0/active_tab/attributes</stringProp> @@ -27094,8 +28039,8 @@ vars.put("visibility", String.valueOf(randomVisibility)); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -27114,8 +28059,11 @@ vars.put("visibility", String.valueOf(randomVisibility)); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -27221,7 +28169,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -27244,11 +28204,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -27325,8 +28285,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -27375,8 +28335,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/</stringProp> @@ -27457,8 +28417,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/validate</stringProp> @@ -27553,8 +28513,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/start</stringProp> @@ -27595,8 +28555,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -27615,8 +28575,11 @@ vars.put("adminImportFilePath", filepath); </stringProp> <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -27722,7 +28685,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -27745,11 +28720,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -27826,8 +28801,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -27876,8 +28851,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/</stringProp> @@ -27958,8 +28933,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/validate</stringProp> @@ -28054,8 +29029,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/import/start</stringProp> @@ -28096,8 +29071,8 @@ vars.put("adminImportFilePath", filepath); </stringProp> </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -28116,8 +29091,11 @@ vars.put("adminImportFilePath", filepath); </stringProp> <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -28223,7 +29201,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -28246,11 +29236,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -28327,8 +29317,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -28365,8 +29355,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/export/</stringProp> @@ -28950,8 +29940,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/export/export/entity/catalog_product/file_format/csv</stringProp> @@ -28982,8 +29972,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -29002,8 +29992,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -29109,7 +30102,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -29132,11 +30137,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -29213,8 +30218,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -29251,8 +30256,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/export/</stringProp> @@ -29491,8 +30496,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/export/export/entity/customer/file_format/csv</stringProp> @@ -29523,8 +30528,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -29543,8 +30548,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -29610,8 +30618,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -29743,8 +30751,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/orders</stringProp> @@ -29779,8 +30787,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/order/${order_id}/invoice</stringProp> @@ -29810,8 +30818,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/order/${order_id}/ship</stringProp> @@ -29888,8 +30896,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attribute-sets/</stringProp> @@ -29941,8 +30949,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attribute-sets/groups</stringProp> @@ -30002,8 +31010,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attributes/</stringProp> @@ -30074,8 +31082,8 @@ if (testLabel </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attribute-sets/attributes</stringProp> @@ -30200,7 +31208,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -30223,11 +31243,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -30304,8 +31324,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -30332,191 +31352,6 @@ vars.put("admin_user", adminUser); </hashTree> </hashTree> - <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/once_only_controller.jmx</stringProp> -</OnceOnlyController> - <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Admin Category Management" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/setup_admin_category_management.jmx</stringProp> -</GenericController> - <hashTree> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Clear Admin Category Management properties" enabled="true"> - <stringProp name="BeanShellSampler.query">props.remove("admin_category_ids_list");</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Get categories of last level" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - API Get categories" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">children_count</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">0</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">level</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">2</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][conditionType]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">gt</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][conditionType]</stringProp> - </elementProp> - <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${adminCategoryCount}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories/list</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">category_list_id</stringProp> - <stringProp name="RegexExtractor.regex">\{\"id\":(\d+),</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Category Id" enabled="true"> - <stringProp name="ForeachController.inputVal">category_list_id</stringProp> - <stringProp name="ForeachController.returnVal">category_id</stringProp> - <boolProp name="ForeachController.useSeparator">true</boolProp> - </ForeachController> - <hashTree> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="Process categories ids" enabled="true"> - <stringProp name="BeanShellSampler.query">import java.util.ArrayList; - -adminCategoryIdsList = props.get("admin_category_ids_list"); -// If it is first iteration of cycle then recreate categories ids list -if (adminCategoryIdsList == null) { - adminCategoryIdsList = new ArrayList(); - props.put("admin_category_ids_list", adminCategoryIdsList); -} -adminCategoryIdsList.add(vars.get("category_id"));</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - </hashTree> - </hashTree> - </hashTree> - </hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Simple Controller" enabled="true"> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/simple_controller.jmx</stringProp> </GenericController> @@ -30548,13 +31383,13 @@ function getNextProductNumber(i) { } var productsVariationsSize = 5, - productsSize = props.get("simple_products_list").size(); + productsSize = props.get("simple_products_list_for_edit").size(); for (i = 1; i<= productsVariationsSize; i++) { var productVariablePrefix = "simple_product_" + i + "_"; number = getNextProductNumber(i); - simpleList = props.get("simple_products_list").get(number); + simpleList = props.get("simple_products_list_for_edit").get(number); vars.put(productVariablePrefix + "url_key", simpleList.get("url_key")); vars.put(productVariablePrefix + "id", simpleList.get("id")); @@ -30575,8 +31410,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/</stringProp> @@ -30626,8 +31461,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/edit/id/${parent_category_id}/</stringProp> @@ -30668,8 +31503,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/add/store/0/parent/${parent_category_id}</stringProp> @@ -30874,8 +31709,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/save/</stringProp> @@ -30915,8 +31750,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/edit/id/${admin_category_id}/</stringProp> @@ -31220,8 +32055,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/move/</stringProp> @@ -31240,8 +32075,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/category/delete/id/${admin_category_id}/</stringProp> @@ -31279,8 +32114,8 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -31299,8 +32134,11 @@ vars.put("new_parent_category_id", props.get("admin_category_ids_list").get(cate <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -31406,7 +32244,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -31429,11 +32279,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -31510,8 +32360,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -31552,8 +32402,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales_rule/promo_quote/</stringProp> @@ -31572,8 +32422,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales_rule/promo_quote/new</stringProp> @@ -31622,8 +32472,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales_rule/promo_quote/newConditionHtml/form/sales_rule_formrule_conditions_fieldset_/form_namespace/sales_rule_form</stringProp> @@ -31909,8 +32759,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales_rule/promo_quote/save/</stringProp> @@ -31948,8 +32798,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -31968,8 +32818,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -32075,7 +32928,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -32098,11 +32963,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -32179,8 +33044,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -32221,8 +33086,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index</stringProp> @@ -32320,8 +33185,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -32407,8 +33272,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -32457,8 +33322,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}${customer_edit_url_path}</stringProp> @@ -33554,7 +34419,7 @@ vars.put("admin_user", adminUser); </elementProp> <elementProp name="address[new_0][region_id]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">12</stringProp> + <stringProp name="Argument.value">${admin_customer_address_region_id}</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">address[new_0][region_id]</stringProp> @@ -33570,8 +34435,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index/validate/</stringProp> @@ -34043,8 +34908,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index/save/</stringProp> @@ -34082,8 +34947,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -34102,8 +34967,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -34209,7 +35077,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -34232,11 +35112,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -34313,8 +35193,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -34351,8 +35231,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order/</stringProp> @@ -34462,8 +35342,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -34579,8 +35459,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> @@ -34681,8 +35561,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order/view/order_id/${order_id}/</stringProp> @@ -34751,8 +35631,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order/addComment/order_id/${order_id}/?isAjax=true</stringProp> @@ -34782,8 +35662,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_invoice/start/order_id/${order_id}/</stringProp> @@ -34853,8 +35733,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/sales/order_invoice/save/order_id/${order_id}/</stringProp> @@ -34884,8 +35764,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/order_shipment/start/order_id/${order_id}/</stringProp> @@ -34945,8 +35825,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/order_shipment/save/order_id/${order_id}/</stringProp> @@ -34978,8 +35858,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -34998,8 +35878,11 @@ vars.put("admin_user", adminUser); <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -35109,7 +35992,19 @@ if (testLabel <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/get_admin_email.jmx</stringProp> <stringProp name="BeanShellSampler.query"> adminUserList = props.get("adminUserList"); -adminUser = adminUserList.poll(); +adminUserListIterator = props.get("adminUserListIterator"); +adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + +if (adminUsersDistribution == 1) { + adminUser = adminUserList.poll(); +} else { + if (!adminUserListIterator.hasNext()) { + adminUserListIterator = adminUserList.descendingIterator(); + } + + adminUser = adminUserListIterator.next(); +} + if (adminUser == null) { SampleResult.setResponseMessage("adminUser list is empty"); SampleResult.setResponseData("adminUser list is empty","UTF-8"); @@ -35132,11 +36027,11 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}</stringProp> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -35213,8 +36108,8 @@ vars.put("admin_user", adminUser); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/dashboard/</stringProp> @@ -35392,8 +36287,8 @@ vars.put("related_product_id", props.get("simple_products_list").get(relatedInde </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -35472,8 +36367,8 @@ vars.put("related_product_id", props.get("simple_products_list").get(relatedInde </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products/attributes</stringProp> @@ -35540,8 +36435,8 @@ vars.putObject("product_attributes", attributes); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product_set/index/filter/${attribute_set_filter}</stringProp> @@ -35584,15 +36479,15 @@ Random random = new Random(); if (${seedForRandom} > 0) { random.setSeed(${seedForRandom}); } -number = random.nextInt(props.get("simple_products_list").size()); -simpleList = props.get("simple_products_list").get(number); +number = random.nextInt(props.get("simple_products_list_for_edit").size()); +simpleList = props.get("simple_products_list_for_edit").get(number); vars.put("simple_product_1_id", simpleList.get("id")); vars.put("simple_product_1_name", simpleList.get("title")); do { - number1 = random.nextInt(props.get("simple_products_list").size()); + number1 = random.nextInt(props.get("simple_products_list_for_edit").size()); } while(number == number1); -simpleList = props.get("simple_products_list").get(number1); +simpleList = props.get("simple_products_list_for_edit").get(number1); vars.put("simple_product_2_id", simpleList.get("id")); vars.put("simple_product_2_name", simpleList.get("title")); @@ -35630,8 +36525,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/</stringProp> @@ -35660,8 +36555,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/new/set/4/type/bundle/</stringProp> @@ -36563,8 +37458,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/validate/set/4/</stringProp> @@ -37497,8 +38392,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/catalog/product/save/set/4/type/bundle/back/edit/active_tab/product-details/</stringProp> @@ -37535,8 +38430,8 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/auth/logout/</stringProp> @@ -37555,8 +38450,11 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <stringProp name="parameters"/> <stringProp name="filename"/> <stringProp name="script"> -adminUserList = props.get("adminUserList"); -adminUserList.add(vars.get("admin_user")); + adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool")); + if (adminUsersDistribution == 1) { + adminUserList = props.get("adminUserList"); + adminUserList.add(vars.get("admin_user")); + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx</stringProp></BeanShellPostProcessor> <hashTree/> @@ -37593,8 +38491,8 @@ adminUserList.add(vars.get("admin_user")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -37693,8 +38591,8 @@ adminUserList.add(vars.get("admin_user")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/products</stringProp> @@ -39806,8 +40704,8 @@ adminUserList.add(vars.get("admin_user")); </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> @@ -40831,8 +41729,8 @@ if (name == null) { </elementProp> <stringProp name="HTTPSampler.domain"/> <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}DeploymentEvent.php</stringProp> diff --git a/setup/pub/angular/angular.js b/setup/pub/angular/angular.js index f53b5280f8647..e3a85e3679ac6 100644 --- a/setup/pub/angular/angular.js +++ b/setup/pub/angular/angular.js @@ -7770,7 +7770,7 @@ * caching. * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} * that should abort the request when resolved. - * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the + * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 * for more information. * - **responseType** - `{string}` - see diff --git a/setup/pub/styles/setup.css b/setup/pub/styles/setup.css index 13dc7b2a043d2..f290b155fb553 100644 --- a/setup/pub/styles/setup.css +++ b/setup/pub/styles/setup.css @@ -3,4 +3,4 @@ * See COPYING.txt for license details. */ -.abs-action-delete,.abs-icon,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.validation-symbol:after{color:#e22626;content:'*';font-weight:400;margin-left:3px}.abs-modal-overlay,.modals-overlay{background:rgba(0,0,0,.35);bottom:0;left:0;position:fixed;right:0;top:0}.abs-action-delete>span,.abs-visually-hidden,.action-multicheck-wrap .action-multicheck-toggle>span,.admin__actions-switch-checkbox,.admin__control-fields .admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label)>.admin__field-label,.admin__field-tooltip .admin__field-tooltip-action span,.customize-your-store .customize-your-store-default .legend,.extensions-information .list .extension-delete>span,.form-el-checkbox,.form-el-radio,.selectmenu .action-delete>span,.selectmenu .action-edit>span,.selectmenu .action-save>span,.selectmenu-toggle span,.tooltip .help a span,.tooltip .help span span,[class*=admin__control-grouped]>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.abs-visually-hidden-reset,.admin__field-group-columns>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label[class]{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.abs-clearfix:after,.abs-clearfix:before,.action-multicheck-wrap:after,.action-multicheck-wrap:before,.actions-split:after,.actions-split:before,.admin__control-table-pagination:after,.admin__control-table-pagination:before,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:before,.admin__data-grid-filters-footer:after,.admin__data-grid-filters-footer:before,.admin__data-grid-filters:after,.admin__data-grid-filters:before,.admin__data-grid-header-row:after,.admin__data-grid-header-row:before,.admin__field-complex:after,.admin__field-complex:before,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .magento-message .insert-title-inner:before,.modal-slide .main-col .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:before,.page-actions._fixed:after,.page-actions._fixed:before,.page-content:after,.page-content:before,.page-header-actions:after,.page-header-actions:before,.page-main-actions:not(._hidden):after,.page-main-actions:not(._hidden):before{content:'';display:table}.abs-clearfix:after,.action-multicheck-wrap:after,.actions-split:after,.admin__control-table-pagination:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-filters-footer:after,.admin__data-grid-filters:after,.admin__data-grid-header-row:after,.admin__field-complex:after,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:after,.page-actions._fixed:after,.page-content:after,.page-header-actions:after,.page-main-actions:not(._hidden):after{clear:both}.abs-list-reset-styles{margin:0;padding:0;list-style:none}.abs-draggable-handle,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle,.admin__control-table .draggable-handle,.data-grid .data-grid-draggable-row-cell .draggable-handle{cursor:-webkit-grab;cursor:move;font-size:0;margin-top:-4px;padding:0 1rem 0 0;vertical-align:middle;display:inline-block;text-decoration:none}.abs-draggable-handle:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:before,.admin__control-table .draggable-handle:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:before{-webkit-font-smoothing:antialiased;font-size:1.8rem;line-height:inherit;color:#9e9e9e;content:'\e617';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.abs-draggable-handle:hover:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:hover:before,.admin__control-table .draggable-handle:hover:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:hover:before{color:#858585}.abs-config-scope-label,.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]:before{bottom:-1.3rem;color:gray;content:attr(data-config-scope);font-size:1.1rem;font-weight:400;min-width:15rem;position:absolute;right:0;text-transform:lowercase}.abs-word-wrap,.admin__field:not(.admin__field-option)>.admin__field-label{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;box-sizing:border-box}*,:after,:before{box-sizing:inherit}:focus{box-shadow:none;outline:0}._keyfocus :focus{box-shadow:0 0 0 1px #008bdb}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}embed,img,object,video{max-width:100%}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/light/opensans-300.eot);src:url(../fonts/opensans/light/opensans-300.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/light/opensans-300.woff2) format('woff2'),url(../fonts/opensans/light/opensans-300.woff) format('woff'),url(../fonts/opensans/light/opensans-300.ttf) format('truetype'),url('../fonts/opensans/light/opensans-300.svg#Open Sans') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/regular/opensans-400.eot);src:url(../fonts/opensans/regular/opensans-400.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/regular/opensans-400.woff2) format('woff2'),url(../fonts/opensans/regular/opensans-400.woff) format('woff'),url(../fonts/opensans/regular/opensans-400.ttf) format('truetype'),url('../fonts/opensans/regular/opensans-400.svg#Open Sans') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/semibold/opensans-600.eot);src:url(../fonts/opensans/semibold/opensans-600.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/semibold/opensans-600.woff2) format('woff2'),url(../fonts/opensans/semibold/opensans-600.woff) format('woff'),url(../fonts/opensans/semibold/opensans-600.ttf) format('truetype'),url('../fonts/opensans/semibold/opensans-600.svg#Open Sans') format('svg');font-weight:600;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/bold/opensans-700.eot);src:url(../fonts/opensans/bold/opensans-700.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/bold/opensans-700.woff2) format('woff2'),url(../fonts/opensans/bold/opensans-700.woff) format('woff'),url(../fonts/opensans/bold/opensans-700.ttf) format('truetype'),url('../fonts/opensans/bold/opensans-700.svg#Open Sans') format('svg');font-weight:700;font-style:normal}html{font-size:62.5%}body{color:#333;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.36;font-size:1.4rem}h1{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2.8rem}h2{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2rem}h3{margin:0 0 2rem;color:#41362f;font-weight:600;line-height:1.2;font-size:1.7rem}h4,h5,h6{font-weight:600;margin-top:0}p{margin:0 0 1em}small{font-size:1.2rem}a{color:#008bdb;text-decoration:none}a:hover{color:#0fa7ff;text-decoration:underline}dl,ol,ul{padding-left:0}nav ol,nav ul{list-style:none;margin:0;padding:0}html{height:100%}body{background-color:#fff;min-height:100%;min-width:102.4rem}.page-wrapper{background-color:#fff;display:inline-block;margin-left:-4px;vertical-align:top;width:calc(100% - 8.8rem)}.page-content{padding-bottom:3rem;padding-left:3rem;padding-right:3rem}.notices-wrapper{margin:0 3rem}.notices-wrapper .messages{margin-bottom:0}.row{margin-left:0;margin-right:0}.row:after{clear:both;content:'';display:table}.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9,.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{min-height:1px;padding-left:0;padding-right:0;position:relative}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}.row-gutter{margin-left:-1.5rem;margin-right:-1.5rem}.row-gutter>[class*=col-]{padding-left:1.5rem;padding-right:1.5rem}.abs-clearer:after,.extension-manager-content:after,.extension-manager-title:after,.form-row:after,.header:after,.nav:after,body:after{clear:both;content:'';display:table}.ng-cloak{display:none!important}.hide.hide{display:none}.show.show{display:block}.text-center{text-align:center}.text-right{text-align:right}@font-face{font-family:Icons;src:url(../fonts/icons/icons.eot);src:url(../fonts/icons/icons.eot?#iefix) format('embedded-opentype'),url(../fonts/icons/icons.woff2) format('woff2'),url(../fonts/icons/icons.woff) format('woff'),url(../fonts/icons/icons.ttf) format('truetype'),url(../fonts/icons/icons.svg#Icons) format('svg');font-weight:400;font-style:normal}[class*=icon-]{display:inline-block;line-height:1}.icon-failed:before,.icon-success:before,[class*=icon-]:after{font-family:Icons}.icon-success{color:#79a22e}.icon-success:before{content:'\e62d'}.icon-failed{color:#e22626}.icon-failed:before{content:'\e632'}.icon-success-thick:after{content:'\e62d'}.icon-collapse:after{content:'\e615'}.icon-failed-thick:after{content:'\e632'}.icon-expand:after{content:'\e616'}.icon-warning:after{content:'\e623'}.icon-failed-round,.icon-success-round{border-radius:100%;color:#fff;font-size:2.5rem;height:1em;position:relative;text-align:center;width:1em}.icon-failed-round:after,.icon-success-round:after{bottom:0;font-size:.5em;left:0;position:absolute;right:0;top:.45em}.icon-success-round{background-color:#79a22e}.icon-success-round:after{content:'\e62d'}.icon-failed-round{background-color:#e22626}.icon-failed-round:after{content:'\e632'}dl,ol,ul{margin-top:0}.list{padding-left:0}.list>li{display:block;margin-bottom:.75em;position:relative}.list>li>.icon-failed,.list>li>.icon-success{font-size:1.6em;left:-.1em;position:absolute;top:0}.list>li>.icon-success{color:#79a22e}.list>li>.icon-failed{color:#e22626}.list-item-failed,.list-item-icon,.list-item-success,.list-item-warning{padding-left:3.5rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{left:-.1em;position:absolute}.list-item-success:before{color:#79a22e}.list-item-failed:before{color:#e22626}.list-item-warning:before{color:#ef672f}.list-definition{margin:0 0 3rem;padding:0}.list-definition>dt{clear:left;float:left}.list-definition>dd{margin-bottom:1em;margin-left:20rem}.btn-wrap{margin:0 auto}.btn-wrap .btn{width:100%}.btn{background:#e3e3e3;border:none;color:#514943;display:inline-block;font-size:1.6rem;font-weight:600;padding:.45em .9em;text-align:center}.btn:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.btn:active{background-color:#d6d6d6}.btn.disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.ie9 .btn.disabled,.ie9 .btn[disabled]{background-color:#f0f0f0;opacity:1;text-shadow:none}.btn-large{padding:.75em 1.25em}.btn-medium{font-size:1.4rem;padding:.5em 1.5em .6em}.btn-link{background-color:transparent;border:none;color:#008bdb;font-family:1.6rem;font-size:1.5rem}.btn-link:active,.btn-link:focus,.btn-link:hover{background-color:transparent;color:#0fa7ff}.btn-prime{background-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.btn-prime:focus,.btn-prime:hover{background-color:#f65405;background-repeat:repeat-x;background-image:linear-gradient(to right,#e04f00 0,#f65405 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e04f00', endColorstr='#f65405', GradientType=1);color:#fff}.btn-prime:active{background-color:#e04f00;background-repeat:repeat-x;background-image:linear-gradient(to right,#f65405 0,#e04f00 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f65405', endColorstr='#e04f00', GradientType=1);color:#fff}.ie9 .btn-prime.disabled,.ie9 .btn-prime[disabled]{background-color:#fd6e23}.ie9 .btn-prime.disabled:active,.ie9 .btn-prime.disabled:hover,.ie9 .btn-prime[disabled]:active,.ie9 .btn-prime[disabled]:hover{background-color:#fd6e23;-webkit-filter:none;filter:none}.btn-secondary{background-color:#514943;color:#fff}.btn-secondary:hover{background-color:#5f564f;color:#fff}.btn-secondary:active,.btn-secondary:focus{background-color:#574e48;color:#fff}.ie9 .btn-secondary.disabled,.ie9 .btn-secondary[disabled]{background-color:#514943}.ie9 .btn-secondary.disabled:active,.ie9 .btn-secondary[disabled]:active{background-color:#514943;-webkit-filter:none;filter:none}[class*=btn-wrap-triangle]{overflow:hidden;position:relative}[class*=btn-wrap-triangle] .btn:after{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.btn-wrap-triangle-right{display:inline-block;padding-right:1.74rem;position:relative}.btn-wrap-triangle-right .btn{text-indent:.92rem}.btn-wrap-triangle-right .btn:after{border-color:transparent transparent transparent #e3e3e3;border-width:1.84rem 0 1.84rem 1.84rem;left:100%;margin-left:-1.74rem}.btn-wrap-triangle-right .btn:focus:after,.btn-wrap-triangle-right .btn:hover:after{border-left-color:#dbdbdb}.btn-wrap-triangle-right .btn:active:after{border-left-color:#d6d6d6}.btn-wrap-triangle-right .btn:not(.disabled):active,.btn-wrap-triangle-right .btn:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn.disabled:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:after{border-color:transparent transparent transparent #f0f0f0}.ie9 .btn-wrap-triangle-right .btn.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn.disabled:focus:after,.ie9 .btn-wrap-triangle-right .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:focus:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:hover:after{border-left-color:#f0f0f0}.btn-wrap-triangle-right .btn-prime:after{border-color:transparent transparent transparent #eb5202}.btn-wrap-triangle-right .btn-prime:focus:after,.btn-wrap-triangle-right .btn-prime:hover:after{border-left-color:#f65405}.btn-wrap-triangle-right .btn-prime:active:after{border-left-color:#e04f00}.btn-wrap-triangle-right .btn-prime:not(.disabled):active,.btn-wrap-triangle-right .btn-prime:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:after{border-color:transparent transparent transparent #fd6e23}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:hover:after{border-left-color:#fd6e23}.btn-wrap-triangle-left{display:inline-block;padding-left:1.74rem}.btn-wrap-triangle-left .btn{text-indent:-.92rem}.btn-wrap-triangle-left .btn:after{border-color:transparent #e3e3e3 transparent transparent;border-width:1.84rem 1.84rem 1.84rem 0;margin-right:-1.74rem;right:100%}.btn-wrap-triangle-left .btn:focus:after,.btn-wrap-triangle-left .btn:hover:after{border-right-color:#dbdbdb}.btn-wrap-triangle-left .btn:active:after{border-right-color:#d6d6d6}.btn-wrap-triangle-left .btn:not(.disabled):active,.btn-wrap-triangle-left .btn:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn.disabled:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:after{border-color:transparent #f0f0f0 transparent transparent}.ie9 .btn-wrap-triangle-left .btn.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:hover:after{border-right-color:#f0f0f0}.btn-wrap-triangle-left .btn-prime:after{border-color:transparent #eb5202 transparent transparent}.btn-wrap-triangle-left .btn-prime:focus:after,.btn-wrap-triangle-left .btn-prime:hover:after{border-right-color:#e04f00}.btn-wrap-triangle-left .btn-prime:active:after{border-right-color:#f65405}.btn-wrap-triangle-left .btn-prime:not(.disabled):active,.btn-wrap-triangle-left .btn-prime:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:after{border-color:transparent #fd6e23 transparent transparent}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:hover:after{border-right-color:#fd6e23}.btn-expand{background-color:transparent;border:none;color:#303030;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700;padding:0;position:relative}.btn-expand.expanded:after{border-color:transparent transparent #303030;border-width:0 .285em .36em}.btn-expand.expanded:hover:after{border-color:transparent transparent #3d3d3d}.btn-expand:hover{background-color:transparent;border:none;color:#3d3d3d}.btn-expand:hover:after{border-color:#3d3d3d transparent transparent}.btn-expand:after{border-color:#303030 transparent transparent;border-style:solid;border-width:.36em .285em 0;content:'';height:0;left:100%;margin-left:.5em;margin-top:-.18em;position:absolute;top:50%;width:0}[class*=col-] .form-el-input,[class*=col-] .form-el-select{width:100%}.form-fieldset{border:none;margin:0 0 1em;padding:0}.form-row{margin-bottom:2.2rem}.form-row .form-row{margin-bottom:.4rem}.form-row .form-label{display:block;font-weight:600;padding:.6rem 2.1em 0 0;text-align:right}.form-row .form-label.required{position:relative}.form-row .form-label.required:after{color:#eb5202;content:'*';font-size:1.15em;position:absolute;right:.7em;top:.5em}.form-row .form-el-checkbox+.form-label:before,.form-row .form-el-radio+.form-label:before{top:.7rem}.form-row .form-el-checkbox+.form-label:after,.form-row .form-el-radio+.form-label:after{top:1.1rem}.form-row.form-row-text{padding-top:.6rem}.form-row.form-row-text .action-sign-out{font-size:1.2rem;margin-left:1rem}.form-note{font-size:1.2rem;font-weight:600;margin-top:1rem}.form-el-dummy{display:none}.fieldset{border:0;margin:0;min-width:0;padding:0}input:not([disabled]):focus,textarea:not([disabled]):focus{box-shadow:none}.form-el-input{border:1px solid #adadad;color:#303030;padding:.35em .55em .5em}.form-el-input:hover{border-color:#949494}.form-el-input:focus{border-color:#008bdb}.form-el-input:required{box-shadow:none}.form-label{margin-bottom:.5em}[class*=form-label][for]{cursor:pointer}.form-el-insider-wrap{display:table;width:100%}.form-el-insider-input{display:table-cell;width:100%}.form-el-insider{border-radius:2px;display:table-cell;padding:.43em .55em .5em 0;vertical-align:top}.form-legend,.form-legend-expand,.form-legend-light{display:block;margin:0}.form-legend,.form-legend-expand{font-size:1.25em;font-weight:600;margin-bottom:2.5em;padding-top:1.5em}.form-legend{border-top:1px solid #ccc;width:100%}.form-legend-light{font-size:1em;margin-bottom:1.5em}.form-legend-expand{cursor:pointer;transition:opacity .2s linear}.form-legend-expand:hover{opacity:.85}.form-legend-expand.expanded:after{content:'\e615'}.form-legend-expand:after{content:'\e616';font-family:Icons;font-size:1.15em;font-weight:400;margin-left:.5em;vertical-align:sub}.form-el-checkbox.disabled+.form-label,.form-el-checkbox.disabled+.form-label:before,.form-el-checkbox[disabled]+.form-label,.form-el-checkbox[disabled]+.form-label:before,.form-el-radio.disabled+.form-label,.form-el-radio.disabled+.form-label:before,.form-el-radio[disabled]+.form-label,.form-el-radio[disabled]+.form-label:before{cursor:default;opacity:.5;pointer-events:none}.form-el-checkbox:not(.disabled)+.form-label:hover:before,.form-el-checkbox:not([disabled])+.form-label:hover:before,.form-el-radio:not(.disabled)+.form-label:hover:before,.form-el-radio:not([disabled])+.form-label:hover:before{border-color:#514943}.form-el-checkbox+.form-label,.form-el-radio+.form-label{font-weight:400;padding-left:2em;padding-right:0;position:relative;text-align:left;transition:border-color .1s linear}.form-el-checkbox+.form-label:before,.form-el-radio+.form-label:before{border:1px solid;content:'';left:0;position:absolute;top:.1rem;transition:border-color .1s linear}.form-el-checkbox+.form-label:before{background-color:#fff;border-color:#adadad;border-radius:2px;font-size:1.2rem;height:1.6rem;line-height:1.2;width:1.6rem}.form-el-checkbox:checked+.form-label::before{content:'\e62d';font-family:Icons}.form-el-radio+.form-label:before{background-color:#fff;border:1px solid #adadad;border-radius:100%;height:1.8rem;width:1.8rem}.form-el-radio+.form-label:after{background:0 0;border:.5rem solid transparent;border-radius:100%;content:'';height:0;left:.4rem;position:absolute;top:.5rem;transition:background .3s linear;width:0}.form-el-radio:checked+.form-label{cursor:default}.form-el-radio:checked+.form-label:after{border-color:#514943}.form-select-label{border:1px solid #adadad;color:#303030;cursor:pointer;display:block;overflow:hidden;position:relative;z-index:0}.form-select-label:hover,.form-select-label:hover:after{border-color:#949494}.form-select-label:active,.form-select-label:active:after,.form-select-label:focus,.form-select-label:focus:after{border-color:#008bdb}.form-select-label:after{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:2.36em;z-index:-2}.ie9 .form-select-label:after{display:none}.form-select-label:before{border-color:#303030 transparent transparent;border-style:solid;border-width:5px 4px 0;content:'';height:0;margin-right:-4px;margin-top:-2.5px;position:absolute;right:1.18em;top:50%;width:0;z-index:-1}.ie9 .form-select-label:before{display:none}.form-select-label .form-el-select{background:0 0;border:none;border-radius:0;content:'';display:block;margin:0;padding:.35em calc(2.36em + 10%) .5em .55em;width:110%}.ie9 .form-select-label .form-el-select{padding-right:.55em;width:100%}.form-select-label .form-el-select::-ms-expand{display:none}.form-el-select{background:#fff;border:1px solid #adadad;border-radius:2px;color:#303030;display:block;padding:.35em .55em}.multiselect-custom{border:1px solid #adadad;height:45.2rem;margin:0 0 1.5rem;overflow:auto;position:relative}.multiselect-custom ul{margin:0;padding:0;list-style:none;min-width:29rem}.multiselect-custom .item{padding:1rem 1.4rem}.multiselect-custom .selected{background-color:#e0f6fe}.multiselect-custom .form-label{margin-bottom:0}[class*=form-el-].invalid{border-color:#e22626}[class*=form-el-].invalid+.error-container{display:block}.error-container{background-color:#fffbbb;border:1px solid #ee7d7d;color:#514943;display:none;font-size:1.19rem;margin-top:.2rem;padding:.8rem 1rem .9rem}.check-result-message{margin-left:.5em;min-height:3.68rem;-ms-align-items:center;-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.check-result-text{margin-left:.5em}body:not([class]){min-width:0}.container{display:block;margin:0 auto 4rem;max-width:100rem;padding:0}.abs-action-delete,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.text-stretch{margin-bottom:1.5em}.page-title-jumbo{font-size:4rem;font-weight:300;letter-spacing:-.05em;margin-bottom:2.9rem}.page-title-jumbo-success:before{color:#79a22e;content:'\e62d';font-size:3.9rem;margin-left:-.3rem;margin-right:2.4rem}.list{margin-bottom:3rem}.list-dot .list-item{display:list-item;list-style-position:inside;margin-bottom:1.2rem}.list-title{color:#333;font-size:1.4rem;font-weight:700;letter-spacing:.025em;margin-bottom:1.2rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{font-family:Icons;font-size:1.6rem;top:0}.list-item-success:before{content:'\e62d';font-size:1.6rem}.list-item-failed:before{content:'\e632';font-size:1.4rem;left:.1rem;top:.2rem}.list-item-warning:before{content:'\e623';font-size:1.3rem;left:.2rem}.form-wrap{margin-bottom:3.6rem;padding-top:2.1rem}.form-el-label-horizontal{display:inline-block;font-size:1.3rem;font-weight:600;letter-spacing:.025em;margin-bottom:.4rem;margin-left:.4rem}.app-updater{min-width:768px}body._has-modal{height:100%;overflow:hidden;width:100%}.modals-overlay{z-index:899}.modal-popup,.modal-slide{bottom:0;min-width:0;position:fixed;right:0;top:0;visibility:hidden}.modal-popup._show,.modal-slide._show{visibility:visible}.modal-popup._show .modal-inner-wrap,.modal-slide._show .modal-inner-wrap{-ms-transform:translate(0,0);transform:translate(0,0)}.modal-popup .modal-inner-wrap,.modal-slide .modal-inner-wrap{background-color:#fff;box-shadow:0 0 12px 2px rgba(0,0,0,.35);opacity:1;pointer-events:auto}.modal-slide{left:14.8rem;z-index:900}.modal-slide._show .modal-inner-wrap{-ms-transform:translateX(0);transform:translateX(0)}.modal-slide .modal-inner-wrap{height:100%;overflow-y:auto;position:static;-ms-transform:translateX(100%);transform:translateX(100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;width:auto}.modal-slide._inner-scroll .modal-inner-wrap{overflow-y:visible;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.modal-slide._inner-scroll .modal-footer,.modal-slide._inner-scroll .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-slide._inner-scroll .modal-content{overflow-y:auto}.modal-slide._inner-scroll .modal-footer{margin-top:auto}.modal-slide .modal-content,.modal-slide .modal-footer,.modal-slide .modal-header{padding:0 2.6rem 2.6rem}.modal-slide .modal-header{padding-bottom:2.1rem;padding-top:2.1rem}.modal-popup{z-index:900;left:0;overflow-y:auto}.modal-popup._show .modal-inner-wrap{-ms-transform:translateY(0);transform:translateY(0)}.modal-popup .modal-inner-wrap{margin:5rem auto;width:75%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;box-sizing:border-box;height:auto;left:0;position:absolute;right:0;-ms-transform:translateY(-200%);transform:translateY(-200%);transition-duration:.2s;transition-property:transform,visibility;transition-timing-function:ease}.modal-popup._inner-scroll{overflow-y:visible}.ie10 .modal-popup._inner-scroll,.ie9 .modal-popup._inner-scroll{overflow-y:auto}.modal-popup._inner-scroll .modal-inner-wrap{max-height:90%}.ie10 .modal-popup._inner-scroll .modal-inner-wrap,.ie9 .modal-popup._inner-scroll .modal-inner-wrap{max-height:none}.modal-popup._inner-scroll .modal-content{overflow-y:auto}.modal-popup .modal-content,.modal-popup .modal-footer,.modal-popup .modal-header{padding-left:3rem;padding-right:3rem}.modal-popup .modal-footer,.modal-popup .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-popup .modal-header{padding-bottom:1.2rem;padding-top:3rem}.modal-popup .modal-footer{margin-top:auto;padding-bottom:3rem}.modal-popup .modal-footer-actions{text-align:right}.admin__action-dropdown-wrap{display:inline-block;position:relative}.admin__action-dropdown-wrap .admin__action-dropdown-text:after{left:-6px;right:0}.admin__action-dropdown-wrap .admin__action-dropdown-menu{left:auto;right:0}.admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__action-dropdown-wrap.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin__action-dropdown-wrap._active .admin__action-dropdown-text:after,.admin__action-dropdown-wrap.active .admin__action-dropdown-text:after{background-color:#fff;content:'';height:6px;position:absolute;top:100%}.admin__action-dropdown-wrap._active .admin__action-dropdown-menu,.admin__action-dropdown-wrap.active .admin__action-dropdown-menu{display:block}.admin__action-dropdown-wrap._disabled .admin__action-dropdown{cursor:default}.admin__action-dropdown-wrap._disabled:hover .admin__action-dropdown{color:#333}.admin__action-dropdown{background-color:#fff;border:1px solid transparent;border-bottom:none;border-radius:0;box-shadow:none;color:#333;display:inline-block;font-size:1.3rem;font-weight:400;letter-spacing:-.025em;padding:.7rem 3.3rem .8rem 1.5rem;position:relative;vertical-align:baseline;z-index:2}.admin__action-dropdown._active:after,.admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .admin__action-dropdown:after,.active .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin__action-dropdown:focus,.admin__action-dropdown:hover{background-color:#fff;color:#000;text-decoration:none}.admin__action-dropdown:after{right:1.5rem}.admin__action-dropdown:before{margin-right:1rem}.admin__action-dropdown-menu{background-color:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;line-height:1.36;margin-top:-1px;min-width:120%;padding:.5rem 1rem;position:absolute;top:100%;transition:all .15s ease;z-index:1}.admin__action-dropdown-menu>li{display:block}.admin__action-dropdown-menu>li>a{color:#333;display:block;text-decoration:none;padding:.6rem .5rem}.selectmenu{display:inline-block;position:relative;text-align:left;z-index:1}.selectmenu._active{border-color:#007bdb;z-index:500}.selectmenu .action-delete,.selectmenu .action-edit,.selectmenu .action-save{background-color:transparent;border-color:transparent;box-shadow:none;padding:0 1rem}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover,.selectmenu .action-save:hover{background-color:transparent;border-color:transparent;box-shadow:none}.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before{content:'\e630'}.selectmenu .action-delete,.selectmenu .action-edit{border:0 solid #fff;border-left-width:1px;bottom:0;position:absolute;right:0;top:0;z-index:1}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover{border:0 solid #fff;border-left-width:1px}.selectmenu .action-save:before{content:'\e625'}.selectmenu .action-edit:before{content:'\e631'}.selectmenu-value{display:inline-block}.selectmenu-value input[type=text]{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:0;display:inline;margin:0;width:6rem}body._keyfocus .selectmenu-value input[type=text]:focus{box-shadow:none}.selectmenu-toggle{padding-right:3rem;background:0 0;border-width:0;bottom:0;float:right;position:absolute;right:0;top:0;width:0}.selectmenu-toggle._active:after,.selectmenu-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.1rem;top:50%;transition:all .2s linear;width:0}._active .selectmenu-toggle:after,.active .selectmenu-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:hover:after{border-color:#000 transparent transparent}.selectmenu-toggle:active,.selectmenu-toggle:focus,.selectmenu-toggle:hover{background:0 0}.selectmenu._active .selectmenu-toggle:before{border-color:#007bdb}body._keyfocus .selectmenu-toggle:focus{box-shadow:none}.selectmenu-toggle:before{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';display:block;position:absolute;right:0;top:0;width:3.2rem}.selectmenu-items{background:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;float:left;left:-1px;margin-top:3px;max-width:20rem;min-width:calc(100% + 2px);position:absolute;top:100%}.selectmenu-items._active{display:block}.selectmenu-items ul{float:left;list-style-type:none;margin:0;min-width:100%;padding:0}.selectmenu-items li{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row;transition:background .2s linear}.selectmenu-items li:hover{background:#e3e3e3}.selectmenu-items li:last-child .selectmenu-item-action,.selectmenu-items li:last-child .selectmenu-item-action:visited{color:#008bdb;text-decoration:none}.selectmenu-items li:last-child .selectmenu-item-action:hover{color:#0fa7ff;text-decoration:underline}.selectmenu-items li:last-child .selectmenu-item-action:active{color:#ff5501;text-decoration:underline}.selectmenu-item{position:relative;width:100%;z-index:1}li._edit>.selectmenu-item{display:none}.selectmenu-item-edit{display:none;padding:.3rem 4rem .3rem .4rem;position:relative;white-space:nowrap;z-index:1}li:last-child .selectmenu-item-edit{padding-right:.4rem}.selectmenu-item-edit .admin__control-text{margin:0;width:5.4rem}li._edit .selectmenu-item-edit{display:block}.selectmenu-item-action{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background:0 0;border:0;color:#333;display:block;font-size:1.4rem;font-weight:400;min-width:100%;padding:1rem 6rem 1rem 1.5rem;text-align:left;transition:background .2s linear;width:5rem}.selectmenu-item-action:focus,.selectmenu-item-action:hover{background:#e3e3e3}.abs-actions-split-xl .action-default,.page-actions .actions-split .action-default{margin-right:4rem}.abs-actions-split-xl .action-toggle,.page-actions .actions-split .action-toggle{padding-right:4rem}.abs-actions-split-xl .action-toggle:after,.page-actions .actions-split .action-toggle:after{border-width:.9rem .6rem 0;margin-top:-.3rem;right:1.4rem}.actions-split{position:relative;z-index:400}.actions-split._active,.actions-split.active,.actions-split:hover{box-shadow:0 0 0 1px #007bdb}.actions-split._active .action-toggle.action-primary,.actions-split._active .action-toggle.primary,.actions-split.active .action-toggle.action-primary,.actions-split.active .action-toggle.primary{background-color:#ba4000;border-color:#ba4000}.actions-split._active .dropdown-menu,.actions-split.active .dropdown-menu{opacity:1;visibility:visible;display:block}.actions-split .action-default,.actions-split .action-toggle{float:left;margin:0}.actions-split .action-default._active,.actions-split .action-default.active,.actions-split .action-default:hover,.actions-split .action-toggle._active,.actions-split .action-toggle.active,.actions-split .action-toggle:hover{box-shadow:none}.actions-split .action-default{margin-right:3.2rem;min-width:9.3rem}.actions-split .action-toggle{padding-right:3.2rem;border-left-color:rgba(0,0,0,.2);bottom:0;padding-left:0;position:absolute;right:0;top:0}.actions-split .action-toggle._active:after,.actions-split .action-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .actions-split .action-toggle:after,.active .actions-split .action-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:hover:after{border-color:#000 transparent transparent}.actions-split .action-toggle.action-primary:after,.actions-split .action-toggle.action-secondary:after,.actions-split .action-toggle.primary:after,.actions-split .action-toggle.secondary:after{border-color:#fff transparent transparent}.actions-split .action-toggle>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-select-wrap{display:inline-block;position:relative}.action-select-wrap .action-select{padding-right:3.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#fff;font-weight:400;text-align:left}.action-select-wrap .action-select._active:after,.action-select-wrap .action-select.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .action-select-wrap .action-select:after,.active .action-select-wrap .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:hover:after{border-color:#000 transparent transparent}.action-select-wrap .action-select:hover,.action-select-wrap .action-select:hover:before{border-color:#878787}.action-select-wrap .action-select:before{background-color:#e3e3e3;border:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:3.2rem}.action-select-wrap .action-select._active{border-color:#007bdb}.action-select-wrap .action-select._active:before{border-color:#007bdb #007bdb #007bdb #adadad}.action-select-wrap .action-select[disabled]{color:#333}.action-select-wrap .action-select[disabled]:after{border-color:#333 transparent transparent}.action-select-wrap._active{z-index:500}.action-select-wrap._active .action-select,.action-select-wrap._active .action-select:before{border-color:#007bdb}.action-select-wrap._active .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .abs-action-menu .action-submenu,.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu,.action-select-wrap .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:45rem;overflow-y:auto}.action-select-wrap .abs-action-menu .action-submenu ._disabled:hover,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .action-menu ._disabled:hover,.action-select-wrap .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled:hover{background:#fff}.action-select-wrap .abs-action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .action-menu ._disabled .action-menu-item,.action-select-wrap .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled .action-menu-item{cursor:default;opacity:.5}.action-select-wrap .action-menu-items{left:0;position:absolute;right:0;top:100%}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu{min-width:100%;position:static}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{position:absolute}.action-multicheck-wrap{display:inline-block;height:1.6rem;padding-top:1px;position:relative;width:3.1rem;z-index:200}.action-multicheck-wrap:hover .action-multicheck-toggle,.action-multicheck-wrap:hover .admin__control-checkbox+label:before{border-color:#878787}.action-multicheck-wrap._active .action-multicheck-toggle,.action-multicheck-wrap._active .admin__control-checkbox+label:before{border-color:#007bdb}.action-multicheck-wrap._active .abs-action-menu .action-submenu,.action-multicheck-wrap._active .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .action-menu,.action-multicheck-wrap._active .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu .action-submenu{opacity:1;visibility:visible;display:block}.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{background-color:#fff}.action-multicheck-wrap._disabled .action-multicheck-toggle,.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{border-color:#adadad;opacity:1}.action-multicheck-wrap .action-multicheck-toggle,.action-multicheck-wrap .admin__control-checkbox,.action-multicheck-wrap .admin__control-checkbox+label{float:left}.action-multicheck-wrap .action-multicheck-toggle{border-radius:0 1px 1px 0;height:1.6rem;margin-left:-1px;padding:0;position:relative;transition:border-color .1s linear;width:1.6rem}.action-multicheck-wrap .action-multicheck-toggle._active:after,.action-multicheck-wrap .action-multicheck-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .action-multicheck-wrap .action-multicheck-toggle:after,.active .action-multicheck-wrap .action-multicheck-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:hover:after{border-color:#000 transparent transparent}.action-multicheck-wrap .action-multicheck-toggle:focus{border-color:#007bdb}.action-multicheck-wrap .action-multicheck-toggle:after{right:.3rem}.action-multicheck-wrap .abs-action-menu .action-submenu,.action-multicheck-wrap .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap .action-menu,.action-multicheck-wrap .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:-1.1rem;margin-top:1px;right:auto;text-align:left}.action-multicheck-wrap .action-menu-item{white-space:nowrap}.admin__action-multiselect-wrap{display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.admin__action-multiselect-wrap.action-select-wrap:focus{box-shadow:none}.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .action-menu,.admin__action-multiselect-wrap.action-select-wrap .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:none;overflow-y:inherit}.admin__action-multiselect-wrap .action-menu-item{transition:background-color .1s linear}.admin__action-multiselect-wrap .action-menu-item._selected{background-color:#e0f6fe}.admin__action-multiselect-wrap .action-menu-item._hover{background-color:#e3e3e3}.admin__action-multiselect-wrap .action-menu-item._unclickable{cursor:default}.admin__action-multiselect-wrap .admin__action-multiselect{border:1px solid #adadad;cursor:pointer;display:block;min-height:3.2rem;padding-right:3.6rem;white-space:normal}.admin__action-multiselect-wrap .admin__action-multiselect:after{bottom:1.25rem;top:auto}.admin__action-multiselect-wrap .admin__action-multiselect:before{height:3.3rem;top:auto}.admin__control-table-wrapper .admin__action-multiselect-wrap{position:static}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect{position:relative}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect:before{right:-1px;top:-1px}.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:34rem;right:auto;top:auto;z-index:1}.admin__action-multiselect-wrap .admin__action-multiselect-item-path{color:#a79d95;font-size:1.2rem;font-weight:400;padding-left:1rem}.admin__action-multiselect-actions-wrap{border-top:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;text-align:center}.admin__action-multiselect-actions-wrap .action-default{font-size:1.3rem;min-width:13rem}.admin__action-multiselect-text{padding:.6rem 1rem}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{text-align:left}.admin__action-multiselect-label{cursor:pointer;position:relative;z-index:1}.admin__action-multiselect-label:before{margin-right:.5rem}._unclickable .admin__action-multiselect-label{cursor:default;font-weight:700}.admin__action-multiselect-search-wrap{border-bottom:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;position:relative}.admin__action-multiselect-search{padding-right:3rem;width:100%}.admin__action-multiselect-search-label{display:block;font-size:1.5rem;height:1em;overflow:hidden;position:absolute;right:2.2rem;top:1.7rem;width:1em}.admin__action-multiselect-search-label:before{content:'\e60c'}.admin__action-multiselect-search-count{color:#a79d95;margin-top:1rem}.admin__action-multiselect-menu-inner{margin-bottom:0;max-height:46rem;overflow-y:auto}.admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{list-style:none;max-height:none;overflow:hidden;padding-left:2.2rem}.admin__action-multiselect-menu-inner ._hidden{display:none}.admin__action-multiselect-crumb{background-color:#f5f5f5;border:1px solid #a79d95;border-radius:1px;display:inline-block;font-size:1.2rem;margin:.3rem -4px .3rem .3rem;padding:.3rem 2.4rem .4rem 1rem;position:relative;transition:border-color .1s linear}.admin__action-multiselect-crumb:hover{border-color:#908379}.admin__action-multiselect-crumb .action-close{bottom:0;font-size:.5em;position:absolute;right:0;top:0;width:2rem}.admin__action-multiselect-crumb .action-close:hover{color:#000}.admin__action-multiselect-crumb .action-close:active,.admin__action-multiselect-crumb .action-close:focus{background-color:transparent}.admin__action-multiselect-crumb .action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__action-multiselect-tree .abs-action-menu .action-submenu,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .action-menu,.admin__action-multiselect-tree .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu{min-width:34.7rem}.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item{margin-top:.1rem}.admin__action-multiselect-tree .action-menu-item{margin-left:4.2rem;position:relative}.admin__action-multiselect-tree .action-menu-item._expended:before{border-left:1px dashed #a79d95;bottom:0;content:'';left:-1rem;position:absolute;top:1rem;width:1px}.admin__action-multiselect-tree .action-menu-item._expended .admin__action-multiselect-dropdown:before{content:'\e615'}.admin__action-multiselect-tree .action-menu-item._with-checkbox .admin__action-multiselect-label{padding-left:2.6rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{padding-left:3.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner:before{left:4.3rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:last-child:before{height:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after,.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{content:'';left:0;position:absolute}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after{border-top:1px dashed #a79d95;height:1px;top:2.1rem;width:5.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{border-left:1px dashed #a79d95;height:100%;top:0;width:1px}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._parent:after{width:4.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root{margin-left:-1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:after{left:3.2rem;width:2.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:before{left:3.2rem;top:1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root._parent:after{display:none}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:first-child:before{top:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:last-child:before{height:1rem}.admin__action-multiselect-tree .admin__action-multiselect-label{line-height:2.2rem;vertical-align:middle;word-break:break-all}.admin__action-multiselect-tree .admin__action-multiselect-label:before{left:0;position:absolute;top:.4rem}.admin__action-multiselect-dropdown{border-radius:50%;height:2.2rem;left:-2.2rem;position:absolute;top:1rem;width:2.2rem;z-index:1}.admin__action-multiselect-dropdown:before{background:#fff;color:#a79d95;content:'\e616';font-size:2.2rem}.admin__actions-switch{display:inline-block;position:relative;vertical-align:middle}.admin__field-control .admin__actions-switch{line-height:3.2rem}.admin__actions-switch+.admin__field-service{min-width:34rem}._disabled .admin__actions-switch-checkbox+.admin__actions-switch-label,.admin__actions-switch-checkbox.disabled+.admin__actions-switch-label{cursor:not-allowed;opacity:.5;pointer-events:none}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:before{left:15px}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:after{background:#79a22e}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label .admin__actions-switch-text:before{content:attr(data-text-on)}.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:after,.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:before{border-color:#007bdb}._error .admin__actions-switch-checkbox+.admin__actions-switch-label:after,._error .admin__actions-switch-checkbox+.admin__actions-switch-label:before{border-color:#e22626}.admin__actions-switch-label{cursor:pointer;display:inline-block;height:22px;line-height:22px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.admin__actions-switch-label:after,.admin__actions-switch-label:before{left:0;position:absolute;right:auto;top:0}.admin__actions-switch-label:before{background:#fff;border:1px solid #aaa6a0;border-radius:100%;content:'';display:block;height:22px;transition:left .2s ease-in 0s;width:22px;z-index:1}.admin__actions-switch-label:after{background:#e3e3e3;border:1px solid #aaa6a0;border-radius:12px;content:'';display:block;height:22px;transition:background .2s ease-in 0s;vertical-align:middle;width:37px;z-index:0}.admin__actions-switch-text:before{content:attr(data-text-off);padding-left:47px;white-space:nowrap}.abs-action-delete,.abs-action-reset,.action-close,.admin__field-fallback-reset,.extensions-information .list .extension-delete,.notifications-close,.search-global-field._active .search-global-action{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0}.abs-action-delete:hover,.abs-action-reset:hover,.action-close:hover,.admin__field-fallback-reset:hover,.extensions-information .list .extension-delete:hover,.notifications-close:hover,.search-global-field._active .search-global-action:hover{background-color:transparent;border:none;box-shadow:none}.abs-action-default,.abs-action-pattern,.abs-action-primary,.abs-action-quaternary,.abs-action-secondary,.abs-action-tertiary,.action-default,.action-primary,.action-quaternary,.action-secondary,.action-tertiary,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions>button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary,button,button.primary,button.secondary,button.tertiary{border:1px solid;border-radius:0;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:1.36;padding:.6rem 1em;text-align:center;vertical-align:baseline}.abs-action-default.disabled,.abs-action-default[disabled],.abs-action-pattern.disabled,.abs-action-pattern[disabled],.abs-action-primary.disabled,.abs-action-primary[disabled],.abs-action-quaternary.disabled,.abs-action-quaternary[disabled],.abs-action-secondary.disabled,.abs-action-secondary[disabled],.abs-action-tertiary.disabled,.abs-action-tertiary[disabled],.action-default.disabled,.action-default[disabled],.action-primary.disabled,.action-primary[disabled],.action-quaternary.disabled,.action-quaternary[disabled],.action-secondary.disabled,.action-secondary[disabled],.action-tertiary.disabled,.action-tertiary[disabled],.modal-popup .modal-footer .action-primary.disabled,.modal-popup .modal-footer .action-primary[disabled],.modal-popup .modal-footer .action-secondary.disabled,.modal-popup .modal-footer .action-secondary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.action-secondary.disabled,.page-actions .page-actions-buttons>button.action-secondary[disabled],.page-actions .page-actions-buttons>button.disabled,.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions .page-actions-buttons>button[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.action-secondary.disabled,.page-actions>button.action-secondary[disabled],.page-actions>button.disabled,.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],.page-actions>button[disabled],button.disabled,button.primary.disabled,button.primary[disabled],button.secondary.disabled,button.secondary[disabled],button.tertiary.disabled,button.tertiary[disabled],button[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-l,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary{font-size:1.6rem;letter-spacing:.025em;padding-bottom:.6875em;padding-top:.6875em}.abs-action-delete,.extensions-information .list .extension-delete{display:inline-block;font-size:1.6rem;margin-left:1.2rem;padding-top:.7rem;text-decoration:none;vertical-align:middle}.abs-action-delete:after,.extensions-information .list .extension-delete:after{color:#666;content:'\e630'}.abs-action-delete:hover:after,.extensions-information .list .extension-delete:hover:after{color:#35302c}.abs-action-button-as-link,.action-advanced,.data-grid .action-delete{line-height:1.36;padding:0;color:#008bdb;text-decoration:none;background:0 0;border:0;display:inline;font-weight:400;border-radius:0}.abs-action-button-as-link:visited,.action-advanced:visited,.data-grid .action-delete:visited{color:#008bdb;text-decoration:none}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{text-decoration:underline}.abs-action-button-as-link:active,.action-advanced:active,.data-grid .action-delete:active{color:#ff5501;text-decoration:underline}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{color:#0fa7ff}.abs-action-button-as-link:active,.abs-action-button-as-link:focus,.abs-action-button-as-link:hover,.action-advanced:active,.action-advanced:focus,.action-advanced:hover,.data-grid .action-delete:active,.data-grid .action-delete:focus,.data-grid .action-delete:hover{background:0 0;border:0}.abs-action-button-as-link.disabled,.abs-action-button-as-link[disabled],.action-advanced.disabled,.action-advanced[disabled],.data-grid .action-delete.disabled,.data-grid .action-delete[disabled],fieldset[disabled] .abs-action-button-as-link,fieldset[disabled] .action-advanced,fieldset[disabled] .data-grid .action-delete{color:#008bdb;opacity:.5;cursor:default;pointer-events:none;text-decoration:underline}.abs-action-button-as-link:active,.abs-action-button-as-link:not(:focus),.action-advanced:active,.action-advanced:not(:focus),.data-grid .action-delete:active,.data-grid .action-delete:not(:focus){box-shadow:none}.abs-action-button-as-link:focus,.action-advanced:focus,.data-grid .action-delete:focus{color:#0fa7ff}.abs-action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.abs-action-default:active,.abs-action-default:focus,.abs-action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.abs-action-primary,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary,button.primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.abs-action-primary:active,.abs-action-primary:focus,.abs-action-primary:hover,.page-actions .page-actions-buttons>button.action-primary:active,.page-actions .page-actions-buttons>button.action-primary:focus,.page-actions .page-actions-buttons>button.action-primary:hover,.page-actions .page-actions-buttons>button.primary:active,.page-actions .page-actions-buttons>button.primary:focus,.page-actions .page-actions-buttons>button.primary:hover,.page-actions>button.action-primary:active,.page-actions>button.action-primary:focus,.page-actions>button.action-primary:hover,.page-actions>button.primary:active,.page-actions>button.primary:focus,.page-actions>button.primary:hover,button.primary:active,button.primary:focus,button.primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-primary.disabled,.abs-action-primary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],button.primary.disabled,button.primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-secondary,.modal-popup .modal-footer .action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions>button.action-secondary,button.secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.abs-action-secondary:active,.abs-action-secondary:focus,.abs-action-secondary:hover,.modal-popup .modal-footer .action-primary:active,.modal-popup .modal-footer .action-primary:focus,.modal-popup .modal-footer .action-primary:hover,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions .page-actions-buttons>button.action-secondary:focus,.page-actions .page-actions-buttons>button.action-secondary:hover,.page-actions>button.action-secondary:active,.page-actions>button.action-secondary:focus,.page-actions>button.action-secondary:hover,button.secondary:active,button.secondary:focus,button.secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-secondary:active,.modal-popup .modal-footer .action-primary:active,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions>button.action-secondary:active,button.secondary:active{background-color:#35302c}.abs-action-tertiary,.modal-popup .modal-footer .action-secondary,button.tertiary{background-color:transparent;border-color:transparent;text-shadow:none;color:#008bdb}.abs-action-tertiary:active,.abs-action-tertiary:focus,.abs-action-tertiary:hover,.modal-popup .modal-footer .action-secondary:active,.modal-popup .modal-footer .action-secondary:focus,.modal-popup .modal-footer .action-secondary:hover,button.tertiary:active,button.tertiary:focus,button.tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#0fa7ff;text-decoration:underline}.abs-action-quaternary,.page-actions .page-actions-buttons>button,.page-actions>button{background-color:transparent;border-color:transparent;text-shadow:none;color:#333}.abs-action-quaternary:active,.abs-action-quaternary:focus,.abs-action-quaternary:hover,.page-actions .page-actions-buttons>button:active,.page-actions .page-actions-buttons>button:focus,.page-actions .page-actions-buttons>button:hover,.page-actions>button:active,.page-actions>button:focus,.page-actions>button:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#1a1a1a}.abs-action-menu,.actions-split .abs-action-menu .action-submenu,.actions-split .abs-action-menu .action-submenu .action-submenu,.actions-split .action-menu,.actions-split .action-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.actions-split .dropdown-menu{text-align:left;background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu._active,.actions-split .abs-action-menu .action-submenu .action-submenu._active,.actions-split .abs-action-menu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .action-menu._active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .actions-split .dropdown-menu .action-submenu._active,.actions-split .dropdown-menu._active{display:block}.abs-action-menu>li,.actions-split .abs-action-menu .action-submenu .action-submenu>li,.actions-split .abs-action-menu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .action-menu>li,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .actions-split .dropdown-menu .action-submenu>li,.actions-split .dropdown-menu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu>li>a:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .abs-action-menu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .action-menu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu>li>a:hover{text-decoration:none}.abs-action-menu>li._visible,.abs-action-menu>li:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu .action-submenu>li:hover,.actions-split .abs-action-menu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .action-menu>li._visible,.actions-split .action-menu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu>li:hover,.actions-split .dropdown-menu>li._visible,.actions-split .dropdown-menu>li:hover{background-color:#e3e3e3}.abs-action-menu>li:active,.actions-split .abs-action-menu .action-submenu .action-submenu>li:active,.actions-split .abs-action-menu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .action-menu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu>li:active,.actions-split .dropdown-menu>li:active{background-color:#cacaca}.abs-action-menu>li._parent,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent,.actions-split .abs-action-menu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .action-menu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent,.actions-split .dropdown-menu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-menu-item,.abs-action-menu .item,.actions-split .abs-action-menu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .item,.actions-split .abs-action-menu .action-submenu .item,.actions-split .action-menu .action-menu-item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .item,.actions-split .action-menu .item,.actions-split .actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .actions-split .dropdown-menu .action-submenu .item,.actions-split .dropdown-menu .action-menu-item,.actions-split .dropdown-menu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu a.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .abs-action-menu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .action-menu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu a.action-menu-item{color:#333}.abs-action-menu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.abs-action-wrap-triangle{position:relative}.abs-action-wrap-triangle .action-default{width:100%}.abs-action-wrap-triangle .action-default:after,.abs-action-wrap-triangle .action-default:before{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.abs-action-wrap-triangle .action-default:active,.abs-action-wrap-triangle .action-default:focus,.abs-action-wrap-triangle .action-default:hover{box-shadow:none}._keyfocus .abs-action-wrap-triangle .action-default:focus{box-shadow:0 0 0 1px #007bdb}.ie10 .abs-action-wrap-triangle .action-default.disabled,.ie10 .abs-action-wrap-triangle .action-default[disabled],.ie9 .abs-action-wrap-triangle .action-default.disabled,.ie9 .abs-action-wrap-triangle .action-default[disabled]{background-color:#fcfcfc;opacity:1;text-shadow:none}.abs-action-wrap-triangle-right{display:inline-block;padding-right:1.6rem;position:relative}.abs-action-wrap-triangle-right .action-default:after,.abs-action-wrap-triangle-right .action-default:before{border-color:transparent transparent transparent #e3e3e3;border-width:1.7rem 0 1.6rem 1.7rem;left:100%;margin-left:-1.7rem}.abs-action-wrap-triangle-right .action-default:before{border-left-color:#949494;right:-1px}.abs-action-wrap-triangle-right .action-default:active:after,.abs-action-wrap-triangle-right .action-default:focus:after,.abs-action-wrap-triangle-right .action-default:hover:after{border-left-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-right .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-right .action-default[disabled]:after{border-color:transparent transparent transparent #fcfcfc}.abs-action-wrap-triangle-right .action-primary:after{border-color:transparent transparent transparent #eb5202}.abs-action-wrap-triangle-right .action-primary:active:after,.abs-action-wrap-triangle-right .action-primary:focus:after,.abs-action-wrap-triangle-right .action-primary:hover:after{border-left-color:#ba4000}.abs-action-wrap-triangle-left{display:inline-block;padding-left:1.6rem}.abs-action-wrap-triangle-left .action-default{text-indent:-.85rem}.abs-action-wrap-triangle-left .action-default:after,.abs-action-wrap-triangle-left .action-default:before{border-color:transparent #e3e3e3 transparent transparent;border-width:1.7rem 1.7rem 1.6rem 0;margin-right:-1.7rem;right:100%}.abs-action-wrap-triangle-left .action-default:before{border-right-color:#949494;left:-1px}.abs-action-wrap-triangle-left .action-default:active:after,.abs-action-wrap-triangle-left .action-default:focus:after,.abs-action-wrap-triangle-left .action-default:hover:after{border-right-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-left .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-left .action-default[disabled]:after{border-color:transparent #fcfcfc transparent transparent}.abs-action-wrap-triangle-left .action-primary:after{border-color:transparent #eb5202 transparent transparent}.abs-action-wrap-triangle-left .action-primary:active:after,.abs-action-wrap-triangle-left .action-primary:focus:after,.abs-action-wrap-triangle-left .action-primary:hover:after{border-right-color:#ba4000}.action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.action-default:active,.action-default:focus,.action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.action-primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.action-primary:active,.action-primary:focus,.action-primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-primary.disabled,.action-primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.action-secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.action-secondary:active,.action-secondary:focus,.action-secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-secondary:active{background-color:#35302c}.action-quaternary,.action-tertiary{background-color:transparent;border-color:transparent;text-shadow:none}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover,.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none}.action-tertiary{color:#008bdb}.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{color:#0fa7ff;text-decoration:underline}.action-quaternary{color:#333}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover{color:#1a1a1a}.action-close>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.action-close:before{content:'\e62f';transition:color .1s linear}.action-close:hover{cursor:pointer;text-decoration:none}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu .action-submenu .action-submenu._active,.abs-action-menu .action-submenu._active,.action-menu .action-submenu._active,.action-menu._active,.actions-split .action-menu .action-submenu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .dropdown-menu .action-submenu._active{display:block}.abs-action-menu .action-submenu .action-submenu>li,.abs-action-menu .action-submenu>li,.action-menu .action-submenu>li,.action-menu>li,.actions-split .action-menu .action-submenu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .dropdown-menu .action-submenu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu .action-submenu .action-submenu>li>a:hover,.abs-action-menu .action-submenu>li>a:hover,.action-menu .action-submenu>li>a:hover,.action-menu>li>a:hover,.actions-split .action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu>li>a:hover{text-decoration:none}.abs-action-menu .action-submenu .action-submenu>li._visible,.abs-action-menu .action-submenu .action-submenu>li:hover,.abs-action-menu .action-submenu>li._visible,.abs-action-menu .action-submenu>li:hover,.action-menu .action-submenu>li._visible,.action-menu .action-submenu>li:hover,.action-menu>li._visible,.action-menu>li:hover,.actions-split .action-menu .action-submenu .action-submenu>li._visible,.actions-split .action-menu .action-submenu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu>li:hover{background-color:#e3e3e3}.abs-action-menu .action-submenu .action-submenu>li:active,.abs-action-menu .action-submenu>li:active,.action-menu .action-submenu>li:active,.action-menu>li:active,.actions-split .action-menu .action-submenu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu>li:active{background-color:#cacaca}.abs-action-menu .action-submenu .action-submenu>li._parent,.abs-action-menu .action-submenu>li._parent,.action-menu .action-submenu>li._parent,.action-menu>li._parent,.actions-split .action-menu .action-submenu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.abs-action-menu .action-submenu>li._parent>.action-menu-item,.action-menu .action-submenu>li._parent>.action-menu-item,.action-menu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .item,.abs-action-menu .action-submenu .item,.action-menu .action-menu-item,.action-menu .action-submenu .action-menu-item,.action-menu .action-submenu .item,.action-menu .item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .item,.actions-split .action-menu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu .action-submenu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu .action-submenu,.ie9 .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .action-menu .action-submenu,.ie9 .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu .action-submenu .action-submenu a.action-menu-item,.abs-action-menu .action-submenu a.action-menu-item,.action-menu .action-submenu a.action-menu-item,.action-menu a.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu a.action-menu-item{color:#333}.abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.abs-action-menu .action-submenu a.action-menu-item:focus,.action-menu .action-submenu a.action-menu-item:focus,.action-menu a.action-menu-item:focus,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.messages .message:last-child{margin:0 0 2rem}.message{background:#fffbbb;border:none;border-radius:0;color:#333;font-size:1.4rem;margin:0 0 1px;padding:1.8rem 4rem 1.8rem 5.5rem;position:relative;text-shadow:none}.message:before{background:0 0;border:0;color:#007bdb;content:'\e61a';font-family:Icons;font-size:1.9rem;font-style:normal;font-weight:400;height:auto;left:1.9rem;line-height:inherit;margin-top:-1.3rem;position:absolute;speak:none;text-shadow:none;top:50%;width:auto}.message-notice:before{color:#007bdb;content:'\e61a'}.message-warning:before{color:#eb5202;content:'\e623'}.message-error{background:#fcc}.message-error:before{color:#e22626;content:'\e632';font-size:1.5rem;left:2.2rem;margin-top:-1rem}.message-success:before{color:#79a22e;content:'\e62d'}.message-spinner:before{display:none}.message-spinner .spinner{font-size:2.5rem;left:1.5rem;position:absolute;top:1.5rem}.message-in-rating-edit{margin-left:1.8rem;margin-right:1.8rem}.modal-popup .action-close,.modal-slide .action-close{color:#736963;position:absolute;right:0;top:0;z-index:1}.modal-popup .action-close:active,.modal-slide .action-close:active{-ms-transform:none;transform:none}.modal-popup .action-close:active:before,.modal-slide .action-close:active:before{font-size:1.8rem}.modal-popup .action-close:hover:before,.modal-slide .action-close:hover:before{color:#58504b}.modal-popup .action-close:before,.modal-slide .action-close:before{font-size:2rem}.modal-popup .action-close:focus,.modal-slide .action-close:focus{background-color:transparent}.modal-popup.prompt .prompt-message{padding:2rem 0}.modal-popup.prompt .prompt-message input{width:100%}.modal-popup.confirm .modal-inner-wrap .message,.modal-popup.prompt .modal-inner-wrap .message{background:#fff}.modal-popup.modal-system-messages .modal-inner-wrap{background:#fffbbb}.modal-popup._image-box .modal-inner-wrap{margin:5rem auto;max-width:78rem;position:static}.modal-popup._image-box .thumbnail-preview{padding-bottom:3rem;text-align:center}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image-block{border:1px solid #ccc;margin:0 auto 2rem;max-width:58rem;padding:2rem}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image{max-height:54rem}.modal-popup .modal-title{font-size:2.4rem;margin-right:6.4rem}.modal-popup .modal-footer{padding-top:2.6rem;text-align:right}.modal-popup .action-close{padding:3rem}.modal-popup .action-close:active,.modal-popup .action-close:focus{background:0 0;padding-right:3.1rem;padding-top:3.1rem}.modal-slide .modal-content-new-attribute{-webkit-overflow-scrolling:touch;overflow:auto;padding-bottom:0}.modal-slide .modal-content-new-attribute iframe{margin-bottom:-2.5rem}.modal-slide .modal-title{font-size:2.1rem;margin-right:5.7rem}.modal-slide .action-close{padding:2.1rem 2.6rem}.modal-slide .action-close:active{padding-right:2.7rem;padding-top:2.2rem}.modal-slide .page-main-actions{margin-bottom:.6rem;margin-top:2.1rem}.modal-slide .magento-message{padding:0 3rem 3rem;position:relative}.modal-slide .magento-message .insert-title-inner,.modal-slide .main-col .insert-title-inner{border-bottom:1px solid #adadad;margin:0 0 2rem;padding-bottom:.5rem}.modal-slide .magento-message .insert-actions,.modal-slide .main-col .insert-actions{float:right}.modal-slide .magento-message .title,.modal-slide .main-col .title{font-size:1.6rem;padding-top:.5rem}.modal-slide .main-col,.modal-slide .side-col{float:left;padding-bottom:0}.modal-slide .main-col:after,.modal-slide .side-col:after{display:none}.modal-slide .side-col{width:20%}.modal-slide .main-col{padding-right:0;width:80%}.modal-slide .content-footer .form-buttons{float:right}.modal-title{font-weight:400;margin-bottom:0;min-height:1em}.modal-title span{font-size:1.4rem;font-style:italic;margin-left:1rem}.spinner{display:inline-block;font-size:4rem;height:1em;margin-right:1.5rem;position:relative;width:1em}.spinner>span:nth-child(1){animation-delay:.27s;-ms-transform:rotate(-315deg);transform:rotate(-315deg)}.spinner>span:nth-child(2){animation-delay:.36s;-ms-transform:rotate(-270deg);transform:rotate(-270deg)}.spinner>span:nth-child(3){animation-delay:.45s;-ms-transform:rotate(-225deg);transform:rotate(-225deg)}.spinner>span:nth-child(4){animation-delay:.54s;-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.spinner>span:nth-child(5){animation-delay:.63s;-ms-transform:rotate(-135deg);transform:rotate(-135deg)}.spinner>span:nth-child(6){animation-delay:.72s;-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.spinner>span:nth-child(7){animation-delay:.81s;-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.spinner>span:nth-child(8){animation-delay:.9;-ms-transform:rotate(0deg);transform:rotate(0deg)}@keyframes fade{0%{background-color:#514943}100%{background-color:#fff}}.spinner>span{-ms-transform:scale(0.4);transform:scale(0.4);animation-name:fade;animation-duration:.72s;animation-iteration-count:infinite;animation-direction:linear;background-color:#fff;border-radius:6px;clip:rect(0 .28571429em .1em 0);height:.1em;margin-top:.5em;position:absolute;width:1em}.ie9 .spinner{background:url(../images/ajax-loader.gif) center no-repeat}.ie9 .spinner>span{display:none}.popup-loading{background:rgba(255,255,255,.8);border-color:#ef672f;color:#ef672f;font-size:14px;font-weight:700;left:50%;margin-left:-100px;padding:100px 0 10px;position:fixed;text-align:center;top:40%;width:200px;z-index:1003}.popup-loading:after{background-image:url(../images/loader-1.gif);content:'';height:64px;left:50%;margin:-32px 0 0 -32px;position:absolute;top:40%;width:64px;z-index:2}.loading-mask,.loading-old{background:rgba(255,255,255,.4);bottom:0;left:0;position:fixed;right:0;top:0;z-index:2003}.loading-mask img,.loading-old img{display:none}.loading-mask p,.loading-old p{margin-top:118px}.loading-mask .loader,.loading-old .loader{background:url(../images/loader-1.gif) 50% 30% no-repeat #f7f3eb;border-radius:5px;bottom:0;color:#575757;font-size:14px;font-weight:700;height:160px;left:0;margin:auto;opacity:.95;position:absolute;right:0;text-align:center;top:0;width:160px}.admin-user{float:right;line-height:1.36;margin-left:.3rem;z-index:490}.admin-user._active .admin__action-dropdown,.admin-user.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin-user .admin__action-dropdown{height:3.3rem;padding:.7rem 2.8rem .4rem 4rem}.admin-user .admin__action-dropdown._active:after,.admin-user .admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:after{border-color:#777 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.3rem;top:50%;transition:all .2s linear;width:0}._active .admin-user .admin__action-dropdown:after,.active .admin-user .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin-user .admin__action-dropdown:before{color:#777;content:'\e600';font-size:2rem;left:1.1rem;margin-top:-1.1rem;position:absolute;top:50%}.admin-user .admin__action-dropdown:hover:before{color:#333}.admin-user .admin__action-dropdown-menu{min-width:20rem;padding-left:1rem;padding-right:1rem}.admin-user .admin__action-dropdown-menu>li>a{padding-left:.5em;padding-right:1.8rem;transition:background-color .1s linear;white-space:nowrap}.admin-user .admin__action-dropdown-menu>li>a:hover{background-color:#e0f6fe;color:#333}.admin-user .admin__action-dropdown-menu>li>a:active{background-color:#c7effd;bottom:-1px;position:relative}.admin-user .admin__action-dropdown-menu .admin-user-name{text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:20rem;overflow:hidden;vertical-align:top}.admin-user-account-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:11.2rem}.search-global{float:right;margin-right:-.3rem;position:relative;z-index:480}.search-global-field{min-width:5rem}.search-global-field._active .search-global-input{background-color:#fff;border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);padding-right:4rem;width:25rem}.search-global-field._active .search-global-action{display:block;height:3.3rem;position:absolute;right:0;text-indent:-100%;top:0;width:5rem;z-index:3}.search-global-field .autocomplete-results{height:3.3rem;position:absolute;right:0;top:0;width:25rem}.search-global-field .search-global-menu{border:1px solid #007bdb;border-top-color:transparent;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin-top:-2px;padding:0;position:absolute;right:0;top:100%;z-index:2}.search-global-field .search-global-menu:after{background-color:#fff;content:'';height:5px;left:0;position:absolute;right:0;top:-5px}.search-global-field .search-global-menu>li{background-color:#fff;border-top:1px solid #ddd;display:block;font-size:1.2rem;padding:.75rem 1.4rem .55rem}.search-global-field .search-global-menu>li._active{background-color:#e0f6fe}.search-global-field .search-global-menu .title{display:block;font-size:1.4rem}.search-global-field .search-global-menu .type{color:#1a1a1a;display:block}.search-global-label{cursor:pointer;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;z-index:2}.search-global-label:active{-ms-transform:scale(0.9);transform:scale(0.9)}.search-global-label:hover:before{color:#000}.search-global-label:before{color:#777;content:'\e60c';font-size:2rem}.search-global-input{background-color:transparent;border:1px solid transparent;font-size:1.4rem;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;transition:all .1s linear,width .3s linear;width:5rem;z-index:1}.search-global-action{display:none}.notifications-wrapper{float:right;line-height:1;position:relative}.notifications-wrapper.active{z-index:500}.notifications-wrapper.active .notifications-action{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.notifications-wrapper.active .notifications-action:after{background-color:#fff;border:none;content:'';display:block;height:6px;left:-6px;margin-top:0;position:absolute;right:0;top:100%;width:auto}.notifications-wrapper .admin__action-dropdown-menu{padding:1rem 0 0;width:32rem}.notifications-action{color:#777;height:3.3rem;padding:.75rem 2rem .65rem}.notifications-action:after{display:none}.notifications-action:before{content:'\e607';font-size:1.9rem;margin-right:0}.notifications-action:active:before{position:relative;top:1px}.notifications-action .notifications-counter{background-color:#e22626;border-radius:1em;color:#fff;display:inline-block;font-size:1.1rem;font-weight:700;left:50%;margin-left:.3em;margin-top:-1.1em;padding:.3em .5em;position:absolute;top:50%}.notifications-entry{line-height:1.36;padding:.6rem 2rem .8rem;position:relative;transition:background-color .1s linear}.notifications-entry:hover{background-color:#e0f6fe}.notifications-entry.notifications-entry-last{margin:0 2rem;padding:.3rem 0 1.3rem;text-align:center}.notifications-entry.notifications-entry-last:hover{background-color:transparent}.notifications-entry+.notifications-entry-last{border-top:1px solid #ddd;padding-bottom:.6rem}.notifications-entry ._cutted{cursor:pointer}.notifications-entry ._cutted .notifications-entry-description-start:after{content:'...'}.notifications-entry-title{color:#ef672f;display:block;font-size:1.1rem;font-weight:700;margin-bottom:.7rem;margin-right:1em}.notifications-entry-description{color:#333;font-size:1.1rem;margin-bottom:.8rem}.notifications-entry-description-end{display:none}.notifications-entry-description-end._show{display:inline}.notifications-entry-time{color:#777;font-size:1.1rem}.notifications-close{line-height:1;padding:1rem;position:absolute;right:0;top:.6rem}.notifications-close:before{color:#ccc;content:'\e620';transition:color .1s linear}.notifications-close:hover:before{color:#b3b3b3}.notifications-close:active{-ms-transform:scale(0.95);transform:scale(0.95)}.page-header-actions{padding-top:1.1rem}.page-header-hgroup{padding-right:1.5rem}.page-title{color:#333;font-size:2.8rem}.page-header{padding:1.5rem 3rem}.menu-wrapper{display:inline-block;position:relative;width:8.8rem;z-index:700}.menu-wrapper:before{background-color:#373330;bottom:0;content:'';left:0;position:fixed;top:0;width:8.8rem;z-index:699}.menu-wrapper._fixed{left:0;position:fixed;top:0}.menu-wrapper._fixed~.page-wrapper{margin-left:8.8rem}.menu-wrapper .logo{display:block;height:8.8rem;padding:2.4rem 0 2.2rem;position:relative;text-align:center;z-index:700}._keyfocus .menu-wrapper .logo:focus{background-color:#4a4542;box-shadow:none}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a{background-color:#373330}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a:after{display:none}.menu-wrapper .logo:hover .logo-img{-webkit-filter:brightness(1.1);filter:brightness(1.1)}.menu-wrapper .logo:active .logo-img{-ms-transform:scale(0.95);transform:scale(0.95)}.menu-wrapper .logo .logo-img{height:4.2rem;transition:-webkit-filter .2s linear,filter .2s linear,transform .1s linear;width:3.5rem}.abs-menu-separator,.admin__menu .item-partners>a:after,.admin__menu .level-0:first-child>a:after{background-color:#736963;content:'';display:block;height:1px;left:0;margin-left:16%;position:absolute;top:0;width:68%}.admin__menu li{display:block}.admin__menu .level-0:first-child>a{position:relative}.admin__menu .level-0._active>a,.admin__menu .level-0:hover>a{color:#f7f3eb}.admin__menu .level-0._active>a{background-color:#524d49}.admin__menu .level-0:hover>a{background-color:#4a4542}.admin__menu .level-0>a{color:#aaa6a0;display:block;font-size:1rem;letter-spacing:.025em;min-height:6.2rem;padding:1.2rem .5rem .5rem;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;transition:background-color .1s linear;word-wrap:break-word;z-index:700}.admin__menu .level-0>a:focus{box-shadow:none}.admin__menu .level-0>a:before{content:'\e63a';display:block;font-size:2.2rem;height:2.2rem}.admin__menu .level-0>.submenu{background-color:#4a4542;box-shadow:0 0 3px #000;left:100%;min-height:calc(8.8rem + 2rem + 100%);padding:2rem 0 0;position:absolute;top:0;-ms-transform:translateX(-100%);transform:translateX(-100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;visibility:hidden;z-index:697}.ie10 .admin__menu .level-0>.submenu,.ie11 .admin__menu .level-0>.submenu{height:100%}.admin__menu .level-0._show>.submenu{-ms-transform:translateX(0);transform:translateX(0);visibility:visible;z-index:698}.admin__menu .level-1{margin-left:1.5rem;margin-right:1.5rem}.admin__menu [class*=level-]:not(.level-0) a{display:block;padding:1.25rem 1.5rem}.admin__menu [class*=level-]:not(.level-0) a:hover{background-color:#403934}.admin__menu [class*=level-]:not(.level-0) a:active{background-color:#322c29;padding-bottom:1.15rem;padding-top:1.35rem}.admin__menu .submenu li{min-width:23.8rem}.admin__menu .submenu a{color:#fcfcfc;transition:background-color .1s linear}.admin__menu .submenu a:focus,.admin__menu .submenu a:hover{box-shadow:none;text-decoration:none}._keyfocus .admin__menu .submenu a:focus{background-color:#403934}._keyfocus .admin__menu .submenu a:active{background-color:#322c29}.admin__menu .submenu .parent{margin-bottom:4.5rem}.admin__menu .submenu .parent .submenu-group-title{color:#a79d95;display:block;font-size:1.6rem;font-weight:600;margin-bottom:.7rem;padding:1.25rem 1.5rem;pointer-events:none}.admin__menu .submenu .column{display:table-cell}.admin__menu .submenu-title{color:#fff;display:block;font-size:2.2rem;font-weight:600;margin-bottom:4.2rem;margin-left:3rem;margin-right:5.8rem}.admin__menu .submenu-sub-title{color:#fff;display:block;font-size:1.2rem;margin:-3.8rem 5.8rem 3.8rem 3rem}.admin__menu .action-close{padding:2.4rem 2.8rem;position:absolute;right:0;top:0}.admin__menu .action-close:before{color:#a79d95;font-size:1.7rem}.admin__menu .action-close:hover:before{color:#fff}.admin__menu .item-dashboard>a:before{content:'\e604';font-size:1.8rem;padding-top:.4rem}.admin__menu .item-sales>a:before{content:'\e60b'}.admin__menu .item-catalog>a:before{content:'\e608'}.admin__menu .item-customer>a:before{content:'\e603';font-size:2.6rem;position:relative;top:-.4rem}.admin__menu .item-marketing>a:before{content:'\e609';font-size:2rem;padding-top:.2rem}.admin__menu .item-content>a:before{content:'\e602';font-size:2.4rem;position:relative;top:-.2rem}.admin__menu .item-report>a:before{content:'\e60a'}.admin__menu .item-stores>a:before{content:'\e60d';font-size:1.9rem;padding-top:.3rem}.admin__menu .item-system>a:before{content:'\e610'}.admin__menu .item-partners._active>a:after,.admin__menu .item-system._current+.item-partners>a:after{display:none}.admin__menu .item-partners>a{padding-bottom:1rem}.admin__menu .item-partners>a:before{content:'\e612'}.admin__menu .level-0>.submenu>ul>.level-1:only-of-type>.submenu-group-title,.admin__menu .submenu .column:only-of-type .submenu-group-title{display:none}.admin__menu-overlay{bottom:0;left:0;position:fixed;right:0;top:0;z-index:697}.store-switcher{color:#333;float:left;font-size:1.3rem;margin-top:.7rem}.store-switcher .admin__action-dropdown{background-color:#f8f8f8;margin-left:.5em}.store-switcher .dropdown{display:inline-block;position:relative}.store-switcher .dropdown:after,.store-switcher .dropdown:before{content:'';display:table}.store-switcher .dropdown:after{clear:both}.store-switcher .dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e607';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle:active:after,.store-switcher .dropdown .action.toggle:hover:after{color:#333}.store-switcher .dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle.active:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e618';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle.active:active:after,.store-switcher .dropdown .action.toggle.active:hover:after{color:#333}.store-switcher .dropdown .dropdown-menu{margin:4px 0 0;padding:0;list-style:none;background:#fff;border:1px solid #aaa6a0;min-width:19.5rem;z-index:100;box-sizing:border-box;display:none;position:absolute;top:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.store-switcher .dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .dropdown .dropdown-menu li:hover{background:0 0;cursor:pointer}.store-switcher .dropdown.active{overflow:visible}.store-switcher .dropdown.active .dropdown-menu{display:block}.store-switcher .dropdown-menu{left:0;margin-top:.5em;max-height:250px;overflow-y:auto;padding-top:.25em}.store-switcher .dropdown-menu li{border:0;cursor:default}.store-switcher .dropdown-menu li:hover{cursor:default}.store-switcher .dropdown-menu li a,.store-switcher .dropdown-menu li span{color:#333;display:block;padding:.5rem 1.3rem}.store-switcher .dropdown-menu li a{text-decoration:none}.store-switcher .dropdown-menu li a:hover{background:#e9e9e9}.store-switcher .dropdown-menu li span{color:#adadad;cursor:default}.store-switcher .dropdown-menu li.current span{background:#eee;color:#333}.store-switcher .dropdown-menu .store-switcher-store a,.store-switcher .dropdown-menu .store-switcher-store span{padding-left:2.6rem}.store-switcher .dropdown-menu .store-switcher-store-view a,.store-switcher .dropdown-menu .store-switcher-store-view span{padding-left:3.9rem}.store-switcher .dropdown-menu .dropdown-toolbar{border-top:1px solid #ebebeb;margin-top:1rem}.store-switcher .dropdown-menu .dropdown-toolbar a:before{content:'\e610';margin-right:.25em;position:relative;top:1px}.store-switcher-label{font-weight:700}.store-switcher-alt{display:inline-block;position:relative}.store-switcher-alt.active .dropdown-menu{display:block}.store-switcher-alt .dropdown-menu{margin-top:2px;white-space:nowrap}.store-switcher-alt .dropdown-menu ul{list-style:none;margin:0;padding:0}.store-switcher-alt strong{color:#a79d95;display:block;font-size:14px;font-weight:500;line-height:1.333;padding:5px 10px}.store-switcher-alt .store-selected{color:#676056;cursor:pointer;font-size:12px;font-weight:400;line-height:1.333}.store-switcher-alt .store-selected:after{-webkit-font-smoothing:antialiased;color:#afadac;content:'\e02c';font-style:normal;font-weight:400;margin:0 0 0 3px;speak:none;vertical-align:text-top}.store-switcher-alt .store-switcher-store,.store-switcher-alt .store-switcher-website{padding:0}.store-switcher-alt .store-switcher-store:hover,.store-switcher-alt .store-switcher-website:hover{background:0 0}.store-switcher-alt .manage-stores,.store-switcher-alt .store-switcher-all,.store-switcher-alt .store-switcher-store-view{padding:0}.store-switcher-alt .manage-stores>a,.store-switcher-alt .store-switcher-all>a{color:#676056;display:block;font-size:12px;padding:8px 15px;text-decoration:none}.store-switcher-website{margin:5px 0 0}.store-switcher-website>strong{padding-left:13px}.store-switcher-store{margin:1px 0 0}.store-switcher-store>strong{padding-left:20px}.store-switcher-store>ul{margin-top:1px}.store-switcher-store-view:first-child{border-top:1px solid #e5e5e5}.store-switcher-store-view>a{color:#333;display:block;font-size:13px;padding:5px 15px 5px 24px;text-decoration:none}.store-view:not(.store-switcher){float:left}.store-view .store-switcher-label{display:inline-block;margin-top:1rem}.tooltip{margin-left:.5em}.tooltip .help a,.tooltip .help span{cursor:pointer;display:inline-block;height:22px;position:relative;vertical-align:middle;width:22px;z-index:2}.tooltip .help a:before,.tooltip .help span:before{color:#333;content:'\e633';font-size:1.7rem}.tooltip .help a:hover{text-decoration:none}.tooltip .tooltip-content{background:#000;border-radius:3px;color:#fff;display:none;margin-left:-19px;margin-top:10px;max-width:200px;padding:4px 8px;position:absolute;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{border-bottom:5px solid #000;border-left:5px solid transparent;border-right:5px solid transparent;content:'';height:0;left:20px;opacity:.8;position:absolute;top:-5px;width:0}.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}.page-actions._fixed,.page-main-actions:not(._hidden){background:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;padding:1.5rem}.page-main-actions{margin:0 0 3rem}.page-main-actions._hidden .store-switcher{display:none}.page-main-actions._hidden .page-actions-placeholder{min-height:50px}.page-actions{float:right}.page-main-actions .page-actions._fixed{left:8.8rem;position:fixed;right:0;top:0;z-index:501}.page-main-actions .page-actions._fixed .page-actions-inner:before{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333;content:attr(data-title);float:left;font-size:2.8rem;margin-top:.3rem;max-width:50%}.page-actions .page-actions-buttons>button,.page-actions>button{float:right;margin-left:1.3rem}.page-actions .page-actions-buttons>button.action-back,.page-actions .page-actions-buttons>button.back,.page-actions>button.action-back,.page-actions>button.back{float:left;-ms-flex-order:-1;order:-1}.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before{content:'\e626';margin-right:.5em;position:relative;top:1px}.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary{-ms-flex-order:2;order:2}.page-actions .page-actions-buttons>button.save:not(.primary),.page-actions>button.save:not(.primary){-ms-flex-order:1;order:1}.page-actions .page-actions-buttons>button.delete,.page-actions>button.delete{-ms-flex-order:-1;order:-1}.page-actions .actions-split{float:right;margin-left:1.3rem;-ms-flex-order:2;order:2}.page-actions .actions-split .dropdown-menu .item{display:block}.page-actions-buttons{float:right;-ms-flex-pack:end;justify-content:flex-end;display:-ms-flexbox;display:flex}.customer-index-edit .page-actions-buttons{background-color:transparent}.admin__page-nav{background:#f1f1f1;border:1px solid #e3e3e3}.admin__page-nav._collapsed:first-child{border-bottom:none}.admin__page-nav._collapsed._show{border-bottom:1px solid #e3e3e3}.admin__page-nav._collapsed._show ._collapsible{background:#f1f1f1}.admin__page-nav._collapsed._show ._collapsible:after{content:'\e62b'}.admin__page-nav._collapsed._show ._collapsible+.admin__page-nav-items{display:block}.admin__page-nav._collapsed._hide .admin__page-nav-title-messages,.admin__page-nav._collapsed._hide .admin__page-nav-title-messages ._active{display:inline-block}.admin__page-nav+._collapsed{border-bottom:none;border-top:none}.admin__page-nav-title{border-bottom:1px solid #e3e3e3;color:#303030;display:block;font-size:1.4rem;line-height:1.2;margin:0 0 -1px;padding:1.8rem 1.5rem;position:relative;text-transform:uppercase}.admin__page-nav-title._collapsible{background:#fff;cursor:pointer;margin:0;padding-right:3.5rem;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-title._collapsible+.admin__page-nav-items{display:none;margin-top:-1px}.admin__page-nav-title._collapsible:after{content:'\e628';font-size:1.3rem;font-weight:700;position:absolute;right:1.8rem;top:2rem}.admin__page-nav-title._collapsible:hover{background:#f1f1f1}.admin__page-nav-title._collapsible:last-child{margin:0 0 -1px}.admin__page-nav-title strong{font-weight:700}.admin__page-nav-title .admin__page-nav-title-messages{display:none}.admin__page-nav-items{list-style-type:none;margin:0;padding:1rem 0 1.3rem}.admin__page-nav-item{border-left:3px solid transparent;margin-left:.7rem;padding:0;position:relative;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-item:hover{border-color:#e4e4e4}.admin__page-nav-item:hover .admin__page-nav-link{background:#e4e4e4;color:#303030;text-decoration:none}.admin__page-nav-item._active,.admin__page-nav-item.ui-state-active{border-color:#eb5202}.admin__page-nav-item._active .admin__page-nav-link,.admin__page-nav-item.ui-state-active .admin__page-nav-link{background:#fff;border-color:#e3e3e3;border-right:1px solid #fff;color:#303030;margin-right:-1px;font-weight:600}.admin__page-nav-item._loading:before,.admin__page-nav-item.ui-tabs-loading:before{display:none}.admin__page-nav-item._loading .admin__page-nav-item-message-loader,.admin__page-nav-item.ui-tabs-loading .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-link{border:1px solid transparent;border-width:1px 0;color:#303030;display:block;font-weight:500;line-height:1.2;margin:0 0 -1px;padding:2rem 4rem 2rem 1rem;transition:border-color .1s ease-out,background-color .1s ease-out;word-wrap:break-word}.admin__page-nav-item-messages{display:inline-block}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-size:1.4rem;font-weight:400;left:-1rem;line-height:1.36;padding:1.5rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after,.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf;margin-top:1px}.admin__page-nav-item-message-loader{display:none;margin-top:-1rem;position:absolute;right:0;top:50%}.admin__page-nav-item-message-loader .spinner{font-size:2rem;margin-right:1.5rem}._loading>.admin__page-nav-item-messages .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-item-message{position:relative}.admin__page-nav-item-message:hover{z-index:500}.admin__page-nav-item-message:hover .admin__page-nav-item-message-tooltip{display:block}.admin__page-nav-item-message._changed,.admin__page-nav-item-message._error{display:none}.admin__page-nav-item-message .admin__page-nav-item-message-icon{display:inline-block;font-size:1.4rem;padding-left:.8em;vertical-align:baseline}.admin__page-nav-item-message .admin__page-nav-item-message-icon:after{color:#666;content:'\e631'}._changed:not(._error)>.admin__page-nav-item-messages ._changed{display:inline-block}._error .admin__page-nav-item-message-icon:after{color:#eb5202;content:'\e623'}._error>.admin__page-nav-item-messages ._error{display:inline-block}._error>.admin__page-nav-item-messages ._error .spinner{font-size:2rem;margin-right:1.5rem}._error .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;left:-1rem;line-height:1.36;padding:2rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}._error .admin__page-nav-item-message-tooltip:after,._error .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}._error .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}._error .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf}.admin__data-grid-wrap-static .data-grid{box-sizing:border-box}.admin__data-grid-wrap-static .data-grid thead{color:#333}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td{background-color:#f5f5f5}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td._dragging{background-color:rgba(245,245,245,.95)}.admin__data-grid-wrap-static .data-grid ul{margin-left:1rem;padding-left:1rem}.admin__data-grid-wrap-static .admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-wrap-static .admin__data-grid-loading-mask .grid-loader{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-filters-actions-wrap{float:right}.data-grid-search-control-wrap{float:left;max-width:45.5rem;position:relative;width:35%}.data-grid-search-control-wrap :-ms-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-webkit-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-moz-placeholder{font-style:italic}.data-grid-search-control-wrap .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:.6rem 2rem .2rem;position:absolute;right:0;top:1px}.data-grid-search-control-wrap .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.data-grid-search-control-wrap .action-submit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.data-grid-search-control-wrap .action-submit:hover:before{color:#1a1a1a}._keyfocus .data-grid-search-control-wrap .action-submit:focus{box-shadow:0 0 0 1px #008bdb}.data-grid-search-control-wrap .action-submit:before{content:'\e60c';font-size:2rem;transition:color .1s linear}.data-grid-search-control-wrap .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.data-grid-search-control-wrap .abs-action-menu .action-submenu,.data-grid-search-control-wrap .abs-action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .action-menu,.data-grid-search-control-wrap .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:19.25rem;overflow-y:auto;z-index:398}.data-grid-search-control-wrap .action-menu-item._selected{background-color:#e0f6fe}.data-grid-search-control-wrap .data-grid-search-label{display:none}.data-grid-search-control{padding-right:6rem;width:100%}.data-grid-filters-action-wrap{float:left;padding-left:2rem}.data-grid-filters-action-wrap .action-default{font-size:1.3rem;margin-bottom:1rem;padding-left:1.7rem;padding-right:2.1rem;padding-top:.7rem}.data-grid-filters-action-wrap .action-default._active{background-color:#fff;border-bottom-color:#fff;border-right-color:#ccc;font-weight:600;margin:-.1rem 0 0;padding-bottom:1.6rem;padding-top:.8rem;position:relative;z-index:281}.data-grid-filters-action-wrap .action-default._active:after{background-color:#eb5202;bottom:100%;content:'';height:3px;left:-1px;position:absolute;right:-1px}.data-grid-filters-action-wrap .action-default:before{color:#333;content:'\e605';font-size:1.8rem;margin-right:.4rem;position:relative;top:-1px;vertical-align:top}.data-grid-filters-action-wrap .filters-active{display:none}.admin__action-grid-select .admin__control-select{margin:-.5rem .5rem 0 0;padding-bottom:.6rem;padding-top:.6rem}.admin__data-grid-filters-wrap{opacity:0;visibility:hidden;clear:both;font-size:1.3rem;transition:opacity .3s ease}.admin__data-grid-filters-wrap._show{opacity:1;visibility:visible;border-bottom:1px solid #ccc;border-top:1px solid #ccc;margin-bottom:.7rem;padding:3.6rem 0 3rem;position:relative;top:-1px;z-index:280}.admin__data-grid-filters-wrap._show .admin__data-grid-filters,.admin__data-grid-filters-wrap._show .admin__data-grid-filters-footer{display:block}.admin__data-grid-filters-wrap .admin__form-field-label,.admin__data-grid-filters-wrap .admin__form-field-legend{display:block;font-weight:700;margin:0 0 .3rem;text-align:left}.admin__data-grid-filters-wrap .admin__form-field{display:inline-block;margin-bottom:2em;margin-left:0;padding-left:2rem;padding-right:2rem;vertical-align:top;width:calc(100% / 4 - 4px)}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field{display:block;float:none;margin-bottom:1.5rem;padding-left:0;padding-right:0;width:auto}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field:last-child{margin-bottom:0}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-label{border:1px solid transparent;float:left;font-weight:400;line-height:1.36;margin-bottom:0;padding-bottom:.6rem;padding-right:1em;padding-top:.6rem;width:25%}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-control{margin-left:25%}.admin__data-grid-filters-wrap .admin__action-multiselect,.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text,.admin__data-grid-filters-wrap .admin__form-field-label{font-size:1.3rem}.admin__data-grid-filters-wrap .admin__control-select{height:3.2rem;padding-top:.5rem}.admin__data-grid-filters-wrap .admin__action-multiselect:before{height:3.2rem;width:3.2rem}.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text._has-datepicker{width:100%}.admin__data-grid-filters{display:none;margin-left:-2rem;margin-right:-2rem}.admin__filters-legend{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-filters-footer{display:none;font-size:1.4rem}.admin__data-grid-filters-footer .admin__footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-filters-footer .admin__footer-secondary-actions{float:left;width:50%}.admin__data-grid-filters-current{border-bottom:.1rem solid #ccc;border-top:.1rem solid #ccc;display:none;font-size:1.3rem;margin-bottom:.9rem;padding-bottom:.8rem;padding-top:1.1rem;width:100%}.admin__data-grid-filters-current._show{display:table;position:relative;top:-1px;z-index:3}.admin__data-grid-filters-current._show+.admin__data-grid-filters-wrap._show{margin-top:-1rem}.admin__current-filters-actions-wrap,.admin__current-filters-list-wrap,.admin__current-filters-title-wrap{display:table-cell;vertical-align:top}.admin__current-filters-title{margin-right:1em;white-space:nowrap}.admin__current-filters-list-wrap{width:100%}.admin__current-filters-list{margin-bottom:0}.admin__current-filters-list>li{display:inline-block;font-weight:600;margin:0 1rem .5rem;padding-right:2.6rem;position:relative}.admin__current-filters-list .action-remove{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0;line-height:1;position:absolute;right:0;top:1px}.admin__current-filters-list .action-remove:hover{background-color:transparent;border:none;box-shadow:none}.admin__current-filters-list .action-remove:hover:before{color:#949494}.admin__current-filters-list .action-remove:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__current-filters-list .action-remove:before{color:#adadad;content:'\e620';font-size:1.6rem;transition:color .1s linear}.admin__current-filters-list .action-remove>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__current-filters-actions-wrap .action-clear{border:none;padding-bottom:0;padding-top:0;white-space:nowrap}.admin__data-grid-pager-wrap{float:right;text-align:right}.admin__data-grid-pager{display:inline-block;margin-left:3rem}.admin__data-grid-pager .admin__control-text::-webkit-inner-spin-button,.admin__data-grid-pager .admin__control-text::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.admin__data-grid-pager .admin__control-text{-moz-appearance:textfield;text-align:center;width:4.4rem}.action-next,.action-previous{width:4.4rem}.action-next:before,.action-previous:before{font-weight:700}.action-next>span,.action-previous>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-previous{margin-right:2.5rem;text-indent:-.25em}.action-previous:before{content:'\e629'}.action-next{margin-left:1.5rem;text-indent:.1em}.action-next:before{content:'\e62a'}.admin__data-grid-action-bookmarks{opacity:.98}.admin__data-grid-action-bookmarks .admin__action-dropdown-text:after{left:0;right:-6px}.admin__data-grid-action-bookmarks._active{z-index:290}.admin__data-grid-action-bookmarks .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:15rem;min-width:4.9rem;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown:before{content:'\e60f'}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu{font-size:1.3rem;left:0;padding:1rem 0;right:auto}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li{padding:0 5rem 0 0;position:relative;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action){transition:background-color .1s linear}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action):hover{background-color:#e3e3e3}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item{max-width:23rem;min-width:18rem;white-space:normal;word-break:break-all}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit{display:none;padding-bottom:1rem;padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit .action-dropdown-menu-item-actions{padding-bottom:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action{padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action+.action-dropdown-menu-item-last{padding-top:.5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a{color:#008bdb;text-decoration:none;display:inline-block;padding-left:1.1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a:hover{color:#0fa7ff;text-decoration:underline}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-last{padding-bottom:0}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item{display:none}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item-edit{display:block}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._active .action-dropdown-menu-link{font-weight:600}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{font-size:1.3rem;min-width:15rem;width:calc(100% - 4rem)}.ie9 .admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{width:15rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-actions{border-left:1px solid #fff;bottom:0;position:absolute;right:0;top:0;width:5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-link{color:#333;display:block;text-decoration:none;padding:1rem 1rem 1rem 2.1rem}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit,.admin__data-grid-action-bookmarks .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;vertical-align:top}.admin__data-grid-action-bookmarks .action-delete:hover,.admin__data-grid-action-bookmarks .action-edit:hover,.admin__data-grid-action-bookmarks .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before{font-size:1.7rem}.admin__data-grid-action-bookmarks .action-delete>span,.admin__data-grid-action-bookmarks .action-edit>span,.admin__data-grid-action-bookmarks .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit{padding:.6rem 1.4rem}.admin__data-grid-action-bookmarks .action-delete:active,.admin__data-grid-action-bookmarks .action-edit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__data-grid-action-bookmarks .action-submit{padding:.6rem 1rem .6rem .8rem}.admin__data-grid-action-bookmarks .action-submit:active{position:relative;right:-1px}.admin__data-grid-action-bookmarks .action-submit:before{content:'\e625'}.admin__data-grid-action-bookmarks .action-delete:before{content:'\e630'}.admin__data-grid-action-bookmarks .action-edit{padding-top:.8rem}.admin__data-grid-action-bookmarks .action-edit:before{content:'\e631'}.admin__data-grid-action-columns._active{opacity:.98;z-index:290}.admin__data-grid-action-columns .admin__action-dropdown:before{content:'\e610';font-size:1.8rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-columns-menu{color:#303030;font-size:1.3rem;overflow:hidden;padding:2.2rem 3.5rem 1rem;z-index:1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-header{border-bottom:1px solid #d1d1d1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-content{width:49.2rem}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-footer{border-top:1px solid #d1d1d1;padding-top:2.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content{max-height:22.85rem;overflow-y:auto;padding-top:1.5rem;position:relative;width:47.4rem}.admin__data-grid-action-columns-menu .admin__field-option{float:left;height:1.9rem;margin-bottom:1.5rem;padding:0 1rem 0 0;width:15.8rem}.admin__data-grid-action-columns-menu .admin__field-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-header{padding-bottom:1.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-footer{padding:1rem 0 2rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-secondary-actions{float:left;margin-left:-1em}.admin__data-grid-action-export._active{opacity:.98;z-index:290}.admin__data-grid-action-export .admin__action-dropdown:before{content:'\e635';font-size:1.7rem;left:.3rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-export-menu{padding-left:2rem;padding-right:2rem;padding-top:1rem}.admin__data-grid-action-export-menu .admin__action-dropdown-footer-main-actions{padding-bottom:2rem;padding-top:2.5rem;white-space:nowrap}.sticky-header{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:8.8rem;margin-top:-1px;padding:.5rem 3rem 0;position:fixed;right:0;top:77px;z-index:398}.sticky-header .admin__data-grid-wrap{margin-bottom:0;overflow-x:visible;padding-bottom:0}.sticky-header .admin__data-grid-header-row{position:relative;text-align:right}.sticky-header .admin__data-grid-header-row:last-child{margin:0}.sticky-header .admin__data-grid-actions-wrap,.sticky-header .admin__data-grid-filters-wrap,.sticky-header .admin__data-grid-pager-wrap,.sticky-header .data-grid-filters-actions-wrap,.sticky-header .data-grid-search-control-wrap{display:inline-block;float:none;vertical-align:top}.sticky-header .action-select-wrap{float:left;margin-right:1.5rem;width:16.66666667%}.sticky-header .admin__control-support-text{float:left}.sticky-header .data-grid-search-control-wrap{margin:-.5rem 0 0 1.1rem;width:auto}.sticky-header .data-grid-search-control-wrap .data-grid-search-label{box-sizing:border-box;cursor:pointer;display:block;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;position:relative;text-align:center}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before{color:#333;content:'\e60c';font-size:2rem;transition:color .1s linear}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:hover:before{color:#000}.sticky-header .data-grid-search-control-wrap .data-grid-search-label span{display:none}.sticky-header .data-grid-filters-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-left:0;position:relative}.sticky-header .data-grid-filters-actions-wrap .action-default{background-color:transparent;border:1px solid transparent;box-sizing:border-box;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;text-align:center;transition:all .15s ease}.sticky-header .data-grid-filters-actions-wrap .action-default span{display:none}.sticky-header .data-grid-filters-actions-wrap .action-default:before{margin:0}.sticky-header .data-grid-filters-actions-wrap .action-default._active{background-color:#fff;border-color:#adadad #adadad #fff;box-shadow:1px 1px 5px rgba(0,0,0,.5);z-index:210}.sticky-header .data-grid-filters-actions-wrap .action-default._active:after{background-color:#fff;content:'';height:6px;left:-2px;position:absolute;right:-6px;top:100%}.sticky-header .data-grid-filters-action-wrap{padding:0}.sticky-header .admin__data-grid-filters-wrap{background-color:#fff;border:1px solid #adadad;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:0;padding-left:3.5rem;padding-right:3.5rem;position:absolute;top:100%;width:100%;z-index:209}.sticky-header .admin__data-grid-filters-current+.admin__data-grid-filters-wrap._show{margin-top:-6px}.sticky-header .filters-active{background-color:#e04f00;border-radius:10px;color:#fff;display:block;font-size:1.4rem;font-weight:700;padding:.1rem .7rem;position:absolute;right:-7px;top:0;z-index:211}.sticky-header .filters-active:empty{padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-right:.3rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown{background-color:transparent;box-sizing:border-box;min-width:3.8rem;padding-left:.6rem;padding-right:.6rem;text-align:center}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:0;min-width:0;overflow:hidden}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:before{margin:0}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap{margin-right:1.1rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after,.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:after{display:none}.sticky-header .admin__data-grid-actions-wrap ._active .admin__action-dropdown{background-color:#fff}.sticky-header .admin__data-grid-action-bookmarks .admin__action-dropdown:before{position:relative;top:-3px}.sticky-header .admin__data-grid-filters-current{border-bottom:0;border-top:0;margin-bottom:0;padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-pager .admin__control-text,.sticky-header .admin__data-grid-pager-wrap .admin__control-support-text,.sticky-header .data-grid-search-control-wrap .action-submit,.sticky-header .data-grid-search-control-wrap .data-grid-search-control{display:none}.sticky-header .action-next{margin:0}.sticky-header .data-grid{margin-bottom:-1px}.data-grid-cap-left,.data-grid-cap-right{background-color:#f8f8f8;bottom:-2px;position:absolute;top:6rem;width:3rem;z-index:201}.data-grid-cap-left{left:0}.admin__data-grid-header{font-size:1.4rem}.admin__data-grid-header-row+.admin__data-grid-header-row{margin-top:1.1rem}.admin__data-grid-header-row:last-child{margin-bottom:0}.admin__data-grid-header-row .action-select-wrap{display:block}.admin__data-grid-header-row .action-select{width:100%}.admin__data-grid-actions-wrap{float:right;margin-left:1.1rem;margin-top:-.5rem;text-align:right}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap{position:relative;text-align:left;vertical-align:middle}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._hide+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:first-child:after{display:none}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown-menu{border-color:#adadad}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after{border-left:1px solid #ccc;content:'';height:3.2rem;left:0;position:absolute;top:.5rem;z-index:3}.admin__data-grid-actions-wrap .admin__action-dropdown{padding-bottom:1.7rem;padding-top:1.2rem}.admin__data-grid-actions-wrap .admin__action-dropdown:after{margin-top:-.4rem}.admin__data-grid-outer-wrap{min-height:8rem;position:relative}.admin__data-grid-wrap{margin-bottom:2rem;max-width:100%;overflow-x:auto;padding-bottom:1rem;padding-top:2rem}.admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-loading-mask .spinner{font-size:4rem;left:50%;margin-left:-2rem;margin-top:-2rem;position:absolute;top:50%}.ie9 .admin__data-grid-loading-mask .spinner{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-cell-content{display:inline-block;overflow:hidden;width:100%}body._in-resize{cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body._in-resize *,body._in-resize .data-grid-th,body._in-resize .data-grid-th._draggable,body._in-resize .data-grid-th._sortable{cursor:col-resize!important}._layout-fixed{table-layout:fixed}.data-grid{border:none;font-size:1.3rem;margin-bottom:0;width:100%}.data-grid:not(._dragging-copy) ._odd-row td._dragging{background-color:#d0d0d0}.data-grid:not(._dragging-copy) ._dragging{background-color:#d9d9d9;color:rgba(48,48,48,.95)}.data-grid:not(._dragging-copy) ._dragging a{color:rgba(0,139,219,.95)}.data-grid:not(._dragging-copy) ._dragging a:hover{color:rgba(15,167,255,.95)}.data-grid._dragged{outline:#007bdb solid 1px}.data-grid thead{background-color:transparent}.data-grid tfoot th{padding:1rem}.data-grid tr._odd-row td{background-color:#f5f5f5}.data-grid tr._odd-row td._update-status-active{background:#89e1ff}.data-grid tr._odd-row td._update-status-upcoming{background:#b7ee63}.data-grid tr:hover td._update-status-active,.data-grid tr:hover td._update-status-upcoming{background-color:#e5f7fe}.data-grid tr.data-grid-tr-no-data td{font-size:1.6rem;padding:3rem;text-align:center}.data-grid tr.data-grid-tr-no-data:hover td{background-color:#fff;cursor:default}.data-grid tr:active td{background-color:#e0f6fe}.data-grid tr:hover td{background-color:#e5f7fe}.data-grid tr._dragged td{background:#d0d0d0}.data-grid tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.data-grid tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.data-grid tr:not(.data-grid-editable-row):last-child td{border-bottom:.1rem solid #d6d6d6}.data-grid tr ._clickable,.data-grid tr._clickable{cursor:pointer}.data-grid tr._disabled{pointer-events:none}.data-grid td,.data-grid th{font-size:1.3rem;line-height:1.36;transition:background-color .1s linear;vertical-align:top}.data-grid td._resizing,.data-grid th._resizing{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid td._hidden,.data-grid th._hidden{display:none}.data-grid td._fit,.data-grid th._fit{width:1%}.data-grid td{background-color:#fff;border-left:.1rem dashed #d6d6d6;border-right:.1rem dashed #d6d6d6;color:#303030;padding:1rem}.data-grid td:first-child{border-left-style:solid}.data-grid td:last-child{border-right-style:solid}.data-grid td .action-select-wrap{position:static}.data-grid td .action-select{color:#008bdb;text-decoration:none;background-color:transparent;border:none;font-size:1.3rem;padding:0 3rem 0 0;position:relative}.data-grid td .action-select:hover{color:#0fa7ff;text-decoration:underline}.data-grid td .action-select:hover:after{border-color:#0fa7ff transparent transparent}.data-grid td .action-select:after{border-color:#008bdb transparent transparent;margin:.6rem 0 0 .7rem;right:auto;top:auto}.data-grid td .action-select:before{display:none}.data-grid td .abs-action-menu .action-submenu,.data-grid td .abs-action-menu .action-submenu .action-submenu,.data-grid td .action-menu,.data-grid td .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:10rem;right:0;text-align:left;top:auto;z-index:1}.data-grid td._update-status-active{background:#bceeff}.data-grid td._update-status-upcoming{background:#ccf391}.data-grid th{background-color:#514943;border:.1rem solid #8a837f;border-left-color:transparent;color:#fff;font-weight:600;padding:0;text-align:left}.data-grid th:first-child{border-left-color:#8a837f}.data-grid th._dragover-left{box-shadow:inset 3px 0 0 0 #fff;z-index:2}.data-grid th._dragover-right{box-shadow:inset -3px 0 0 0 #fff}.data-grid .shadow-div{cursor:col-resize;height:100%;margin-right:-5px;position:absolute;right:0;top:0;width:10px}.data-grid .data-grid-th{background-clip:padding-box;color:#fff;padding:1rem;position:relative;vertical-align:middle}.data-grid .data-grid-th._resize-visible .shadow-div{cursor:auto;display:none}.data-grid .data-grid-th._draggable{cursor:grab}.data-grid .data-grid-th._sortable{cursor:pointer;transition:background-color .1s linear;z-index:1}.data-grid .data-grid-th._sortable:focus,.data-grid .data-grid-th._sortable:hover{background-color:#5f564f}.data-grid .data-grid-th._sortable:active{padding-bottom:.9rem;padding-top:1.1rem}.data-grid .data-grid-th.required>span:after{color:#f38a5e;content:'*';margin-left:.3rem}.data-grid .data-grid-checkbox-cell{overflow:hidden;padding:0;vertical-align:top;width:5.2rem}.data-grid .data-grid-checkbox-cell:hover{cursor:default}.data-grid .data-grid-thumbnail-cell{text-align:center;width:7rem}.data-grid .data-grid-thumbnail-cell img{border:1px solid #d6d6d6;width:5rem}.data-grid .data-grid-multicheck-cell{padding:1rem 1rem .9rem;text-align:center;vertical-align:middle}.data-grid .data-grid-onoff-cell{text-align:center;width:12rem}.data-grid .data-grid-actions-cell{padding-left:2rem;padding-right:2rem;text-align:center;width:1%}.data-grid._hidden{display:none}.data-grid._dragging-copy{box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;opacity:.95;position:fixed;top:0;z-index:1000}.data-grid._dragging-copy .data-grid-th{border:1px solid #007bdb;border-bottom:none}.data-grid._dragging-copy .data-grid-th,.data-grid._dragging-copy .data-grid-th._sortable{cursor:grabbing}.data-grid._dragging-copy tr:last-child td{border-bottom:1px solid #007bdb}.data-grid._dragging-copy td{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:rgba(255,251,230,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td,.data-grid._dragging-copy._in-edit .data-grid-editable-row:hover td{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:after,.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{left:0;right:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:only-child{border-left:1px solid #007bdb;border-right:1px solid #007bdb;left:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-select,.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-text{opacity:.5}.data-grid .data-grid-controls-row td{padding-top:1.6rem}.data-grid .data-grid-controls-row td.data-grid-checkbox-cell{padding-top:.6rem}.data-grid .data-grid-controls-row td [class*=admin__control-],.data-grid .data-grid-controls-row td button{margin-top:-1.7rem}.data-grid._in-edit tr:hover td{background-color:#e6e6e6}.data-grid._in-edit ._odd-row.data-grid-editable-row td,.data-grid._in-edit ._odd-row.data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit ._odd-row td,.data-grid._in-edit ._odd-row:hover td{background-color:#dcdcdc}.data-grid._in-edit .data-grid-editable-row-actions td,.data-grid._in-edit .data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid._in-edit td{background-color:#e6e6e6;pointer-events:none}.data-grid._in-edit .data-grid-checkbox-cell{pointer-events:auto}.data-grid._in-edit .data-grid-editable-row{border:.1rem solid #adadad;border-bottom-color:#c2c2c2}.data-grid._in-edit .data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit .data-grid-editable-row td{background-color:#fff;border-bottom-color:#fff;border-left-style:hidden;border-right-style:hidden;border-top-color:#fff;pointer-events:auto;vertical-align:middle}.data-grid._in-edit .data-grid-editable-row td:first-child{border-left-color:#adadad;border-left-style:solid}.data-grid._in-edit .data-grid-editable-row td:first-child:after,.data-grid._in-edit .data-grid-editable-row td:first-child:before{left:0}.data-grid._in-edit .data-grid-editable-row td:last-child{border-right-color:#adadad;border-right-style:solid;left:-.1rem}.data-grid._in-edit .data-grid-editable-row td:last-child:after,.data-grid._in-edit .data-grid-editable-row td:last-child:before{right:0}.data-grid._in-edit .data-grid-editable-row .admin__control-select,.data-grid._in-edit .data-grid-editable-row .admin__control-text{width:100%}.data-grid._in-edit .data-grid-bulk-edit-panel td{vertical-align:bottom}.data-grid .data-grid-editable-row td{border-left-color:#fff;border-left-style:solid;position:relative;z-index:1}.data-grid .data-grid-editable-row td:after{bottom:0;box-shadow:0 5px 5px rgba(0,0,0,.25);content:'';height:.9rem;left:0;margin-top:-1rem;position:absolute;right:0}.data-grid .data-grid-editable-row td:before{background-color:#fff;bottom:0;content:'';height:1rem;left:-10px;position:absolute;right:-10px;z-index:1}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td,.data-grid .data-grid-editable-row.data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:first-child{border-left-color:#fff;border-right-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:last-child{left:0}.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:#fffbe6}.data-grid .data-grid-editable-row-actions{left:50%;margin-left:-12.5rem;margin-top:-2px;position:absolute;text-align:center}.data-grid .data-grid-editable-row-actions td{width:25rem}.data-grid .data-grid-editable-row-actions [class*=action-]{min-width:9rem}.data-grid .data-grid-draggable-row-cell{width:1%}.data-grid .data-grid-draggable-row-cell .draggable-handle{padding:0}.data-grid-th._sortable._ascend,.data-grid-th._sortable._descend{padding-right:2.7rem}.data-grid-th._sortable._ascend:before,.data-grid-th._sortable._descend:before{margin-top:-1em;position:absolute;right:1rem;top:50%}.data-grid-th._sortable._ascend:before{content:'\2193'}.data-grid-th._sortable._descend:before{content:'\2191'}.data-grid-checkbox-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:right}.data-grid-checkbox-cell-inner:hover{cursor:pointer}.data-grid-state-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:center}.data-grid-state-cell-inner>span{display:inline-block;font-style:italic;padding:.6rem 0}.data-grid-row-parent._active>td .data-grid-checkbox-cell-inner:before{content:'\e62b'}.data-grid-row-parent>td .data-grid-checkbox-cell-inner{padding-left:3.7rem;position:relative}.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before{content:'\e628';font-size:1rem;font-weight:700;left:1.35rem;position:absolute;top:1.6rem}.data-grid-th._col-xs{width:1%}.data-grid-info-panel{box-shadow:0 0 5px rgba(0,0,0,.5);margin:2rem .1rem -2rem}.data-grid-info-panel .messages{overflow:hidden}.data-grid-info-panel .messages .message{margin:1rem}.data-grid-info-panel .messages .message:last-child{margin-bottom:1rem}.data-grid-info-panel-actions{padding:1rem;text-align:right}.data-grid-editable-row .admin__field-control{position:relative}.data-grid-editable-row .admin__field-control._error:after{border-color:transparent #ee7d7d transparent transparent;border-style:solid;border-width:0 12px 12px 0;content:'';position:absolute;right:0;top:0}.data-grid-editable-row .admin__field-control._error .admin__control-text{border-color:#ee7d7d}.data-grid-editable-row .admin__field-control._focus:after{display:none}.data-grid-editable-row .admin__field-error{bottom:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin:0 auto 1.5rem;max-width:32rem;position:absolute;right:0}.data-grid-editable-row .admin__field-error:after,.data-grid-editable-row .admin__field-error:before{border-style:solid;content:'';left:50%;position:absolute;top:100%}.data-grid-editable-row .admin__field-error:after{border-color:#fffbbb transparent transparent;border-width:10px 10px 0;margin-left:-10px;z-index:1}.data-grid-editable-row .admin__field-error:before{border-color:#ee7d7d transparent transparent;border-width:11px 12px 0;margin-left:-12px}.data-grid-bulk-edit-panel .admin__field-label-vertical{display:block;font-size:1.2rem;margin-bottom:.5rem;text-align:left}.data-grid-row-changed{cursor:default;display:block;opacity:.5;position:relative;width:100%;z-index:1}.data-grid-row-changed:after{content:'\e631';display:inline-block}.data-grid-row-changed .data-grid-row-changed-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:100%;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;line-height:1.36;margin-bottom:1.5rem;padding:1rem;position:absolute;right:-1rem;text-transform:none;width:27rem;word-break:normal;z-index:2}.data-grid-row-changed._changed{opacity:1;z-index:3}.data-grid-row-changed._changed:hover .data-grid-row-changed-tooltip{display:block}.data-grid-row-changed._changed:hover:before{background:#f1f1f1;border:1px solid #f1f1f1;bottom:100%;box-shadow:4px 4px 3px -1px rgba(0,0,0,.15);content:'';display:block;height:1.6rem;left:50%;margin:0 0 .7rem -.8rem;position:absolute;-ms-transform:rotate(45deg);transform:rotate(45deg);width:1.6rem;z-index:3}.ie9 .data-grid-row-changed._changed:hover:before{display:none}.admin__data-grid-outer-wrap .data-grid-checkbox-cell{overflow:hidden}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner{position:relative}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner:before{bottom:0;content:'';height:500%;left:0;position:absolute;right:0;top:0}.admin__data-grid-wrap-static .data-grid-checkbox-cell:hover{cursor:pointer}.admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:1.1rem 1.8rem .9rem;padding:0}.adminhtml-cms-hierarchy-index .admin__data-grid-wrap-static .data-grid-actions-cell:first-child{padding:0}.adminhtml-export-index .admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:0;padding:1.1rem 1.8rem 1.9rem}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before,.admin__control-file-label:before,.admin__control-multiselect,.admin__control-select,.admin__control-text,.admin__control-textarea,.selectmenu{-webkit-appearance:none;background-color:#fff;border:1px solid #adadad;border-radius:1px;box-shadow:none;color:#303030;font-size:1.4rem;font-weight:400;height:auto;line-height:1.36;padding:.6rem 1rem;transition:border-color .1s linear;vertical-align:baseline;width:auto}.admin__control-addon [class*=admin__control-][class]:hover~[class*=admin__addon-]:last-child:before,.admin__control-multiselect:hover,.admin__control-select:hover,.admin__control-text:hover,.admin__control-textarea:hover,.selectmenu:hover,.selectmenu:hover .selectmenu-toggle:before{border-color:#878787}.admin__control-addon [class*=admin__control-][class]:focus~[class*=admin__addon-]:last-child:before,.admin__control-file:active+.admin__control-file-label:before,.admin__control-file:focus+.admin__control-file-label:before,.admin__control-multiselect:focus,.admin__control-select:focus,.admin__control-text:focus,.admin__control-textarea:focus,.selectmenu._focus,.selectmenu._focus .selectmenu-toggle:before{border-color:#007bdb;box-shadow:none;outline:0}.admin__control-addon [class*=admin__control-][class][disabled]~[class*=admin__addon-]:last-child:before,.admin__control-file[disabled]+.admin__control-file-label:before,.admin__control-multiselect[disabled],.admin__control-select[disabled],.admin__control-text[disabled],.admin__control-textarea[disabled]{background-color:#e9e9e9;border-color:#adadad;color:#303030;cursor:not-allowed;opacity:.5}.admin__field-row[class]>.admin__field-control,.admin__fieldset>.admin__field.admin__field-wide[class]>.admin__field-control{clear:left;float:none;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label{display:block;line-height:1.4rem;margin-bottom:.86rem;margin-top:-.14rem;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label:before,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label:before{display:none}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span{padding-left:1.5rem}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span:after{left:0;margin-left:30px}.admin__legend{font-size:1.8rem;font-weight:600;margin-bottom:3rem}.admin__control-checkbox,.admin__control-radio{cursor:pointer;opacity:.01;overflow:hidden;position:absolute;vertical-align:top}.admin__control-checkbox:after,.admin__control-radio:after{display:none}.admin__control-checkbox+label,.admin__control-radio+label{cursor:pointer;display:inline-block}.admin__control-checkbox+label:before,.admin__control-radio+label:before{background-color:#fff;border:1px solid #adadad;color:transparent;float:left;height:1.6rem;text-align:center;vertical-align:top;width:1.6rem}.admin__control-checkbox+.admin__field-label,.admin__control-radio+.admin__field-label{padding-left:2.6rem}.admin__control-checkbox+.admin__field-label:before,.admin__control-radio+.admin__field-label:before{margin:1px 1rem 0 -2.6rem}.admin__control-checkbox:checked+label:before,.admin__control-radio:checked+label:before{color:#514943}.admin__control-checkbox.disabled+label,.admin__control-checkbox[disabled]+label,.admin__control-radio.disabled+label,.admin__control-radio[disabled]+label{color:#303030;cursor:default;opacity:.5}.admin__control-checkbox.disabled+label:before,.admin__control-checkbox[disabled]+label:before,.admin__control-radio.disabled+label:before,.admin__control-radio[disabled]+label:before{background-color:#e9e9e9;border-color:#adadad;cursor:default}._keyfocus .admin__control-checkbox:not(.disabled):focus+label:before,._keyfocus .admin__control-checkbox:not([disabled]):focus+label:before,._keyfocus .admin__control-radio:not(.disabled):focus+label:before,._keyfocus .admin__control-radio:not([disabled]):focus+label:before{border-color:#007bdb}.admin__control-checkbox:not(.disabled):hover+label:before,.admin__control-checkbox:not([disabled]):hover+label:before,.admin__control-radio:not(.disabled):hover+label:before,.admin__control-radio:not([disabled]):hover+label:before{border-color:#878787}.admin__control-radio+label:before{border-radius:1.6rem;content:'';transition:border-color .1s linear,color .1s ease-in}.admin__control-radio.admin__control-radio+label:before{line-height:140%}.admin__control-radio:checked+label{position:relative}.admin__control-radio:checked+label:after{background-color:#514943;border-radius:50%;content:'';height:10px;left:3px;position:absolute;top:4px;width:10px}.admin__control-radio:checked:not(.disabled):hover,.admin__control-radio:checked:not(.disabled):hover+label,.admin__control-radio:checked:not([disabled]):hover,.admin__control-radio:checked:not([disabled]):hover+label{cursor:default}.admin__control-radio:checked:not(.disabled):hover+label:before,.admin__control-radio:checked:not([disabled]):hover+label:before{border-color:#adadad}.admin__control-checkbox+label:before{border-radius:1px;content:'';font-size:0;transition:font-size .1s ease-out,color .1s ease-out,border-color .1s linear}.admin__control-checkbox:checked+label:before{content:'\e62d';font-size:1.1rem;line-height:125%}.admin__control-checkbox:not(:checked)._indeterminate+label:before,.admin__control-checkbox:not(:checked):indeterminate+label:before{color:#514943;content:'-';font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700}input[type=checkbox].admin__control-checkbox,input[type=radio].admin__control-checkbox{margin:0;position:absolute}.admin__control-text{min-width:4rem}.admin__control-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#adadad,#adadad);background-position:calc(100% - 12px) -34px,100%,calc(100% - 3.2rem) 0;background-size:auto,3.2rem 100%,1px 100%;background-repeat:no-repeat;max-width:100%;min-width:8.5rem;padding-bottom:.5rem;padding-right:4.4rem;padding-top:.5rem;transition:border-color .1s linear}.admin__control-select:hover{border-color:#878787;cursor:pointer}.admin__control-select:focus{background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#007bdb,#007bdb);background-position:calc(100% - 12px) 13px,100%,calc(100% - 3.2rem) 0;border-color:#007bdb}.admin__control-select::-ms-expand{display:none}.ie9 .admin__control-select{background-image:none;padding-right:1rem}option:empty{display:none}.admin__control-multiselect{height:auto;max-width:100%;min-width:15rem;overflow:auto;padding:0;resize:both}.admin__control-multiselect optgroup,.admin__control-multiselect option{padding:.5rem 1rem}.admin__control-file-wrapper{display:inline-block;padding:.5rem 1rem;position:relative;z-index:1}.admin__control-file-label:before{content:'';left:0;position:absolute;top:0;width:100%;z-index:0}.admin__control-file{background:0 0;border:0;padding-top:.7rem;position:relative;width:auto;z-index:1}.admin__control-support-text{border:1px solid transparent;display:inline-block;font-size:1.4rem;line-height:1.36;padding-bottom:.6rem;padding-top:.6rem}.admin__control-support-text+[class*=admin__control-],[class*=admin__control-]+.admin__control-support-text{margin-left:.7rem}.admin__control-service{float:left;margin:.8rem 0 0 3rem}.admin__control-textarea{height:8.48rem;line-height:1.18;padding-top:.8rem;resize:vertical}.admin__control-addon{-ms-flex-direction:row;flex-direction:row;display:inline-flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;position:relative;width:100%;z-index:1}.admin__control-addon>[class*=admin__addon-],.admin__control-addon>[class*=admin__control-]{-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;position:relative;z-index:1}.admin__control-addon .admin__control-select{width:auto}.admin__control-addon .admin__control-text{margin:.1rem;padding:.5rem .9rem;width:100%}.admin__control-addon [class*=admin__control-][class]{appearence:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-order:1;order:1;-ms-flex-negative:1;flex-shrink:1;background-color:transparent;border-color:transparent;box-shadow:none;vertical-align:top}.admin__control-addon [class*=admin__control-][class]+[class*=admin__control-]{border-left-color:#adadad}.admin__control-addon [class*=admin__control-][class] :focus{box-shadow:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child{padding-left:1rem;position:static!important;z-index:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child>*{position:relative;vertical-align:top;z-index:1}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:empty{padding:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before{bottom:0;box-sizing:border-box;content:'';left:0;position:absolute;top:0;width:100%;z-index:-1}.admin__addon-prefix,.admin__addon-suffix{border:0;box-sizing:border-box;color:#858585;display:inline-block;font-size:1.4rem;font-weight:400;height:3.2rem;line-height:3.2rem;padding:0}.admin__addon-suffix{-ms-flex-order:3;order:3}.admin__addon-suffix:last-child{padding-right:1rem}.admin__addon-prefix{-ms-flex-order:0;order:0}.ie9 .admin__control-addon:after{clear:both;content:'';display:block;height:0;overflow:hidden}.ie9 .admin__addon{min-width:0;overflow:hidden;text-align:right;white-space:nowrap;width:auto}.ie9 .admin__addon [class*=admin__control-]{display:inline}.ie9 .admin__addon-prefix{float:left}.ie9 .admin__addon-suffix{float:right}.admin__control-collapsible{width:100%}.admin__control-collapsible ._dragged .admin__collapsible-block-wrapper .admin__collapsible-title{background:#d0d0d0}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before,.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{background:#008bdb;content:'';display:block;height:3px;left:0;position:absolute;right:0}.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{top:-3px}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before{bottom:-3px}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper{border:0;margin:0;position:relative}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper .fieldset-wrapper-title{background:#f8f8f8;border:2px solid #ccc}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title{font-size:1.4rem;font-weight:400;line-height:1;padding:1.6rem 4rem 1.6rem 3.8rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title:before{left:1rem;right:auto;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding:0;position:absolute;right:1rem;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before{content:'\e630';font-size:2rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete>span{display:none}.admin__control-collapsible .admin__collapsible-content{background-color:#fff;margin-bottom:1rem}.admin__control-collapsible .admin__collapsible-content>.fieldset-wrapper{border:1px solid #ccc;margin-top:-1px;padding:1rem}.admin__control-collapsible .admin__collapsible-content .admin__fieldset{padding:0}.admin__control-collapsible .admin__collapsible-content .admin__field:last-child{margin-bottom:0}.admin__control-table-wrapper{max-width:100%;overflow-x:auto;overflow-y:hidden}.admin__control-table{width:100%}.admin__control-table thead{background-color:transparent}.admin__control-table tbody td{vertical-align:top}.admin__control-table tfoot th{padding-bottom:1.3rem}.admin__control-table tfoot th.validation{padding-bottom:0;padding-top:0}.admin__control-table tfoot td{border-top:1px solid #fff}.admin__control-table tfoot .admin__control-table-pagination{float:right;padding-bottom:0}.admin__control-table tfoot .action-previous{margin-right:.5rem}.admin__control-table tfoot .action-next{margin-left:.9rem}.admin__control-table tr:last-child td{border-bottom:none}.admin__control-table tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.admin__control-table tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.admin__control-table tr._dragged td,.admin__control-table tr._dragged th{background:#d0d0d0}.admin__control-table td,.admin__control-table th{background-color:#efefef;border:0;border-bottom:1px solid #fff;padding:1.3rem 1rem 1.3rem 0;text-align:left;vertical-align:top}.admin__control-table td:first-child,.admin__control-table th:first-child{padding-left:1rem}.admin__control-table td>.admin__control-select,.admin__control-table td>.admin__control-text,.admin__control-table th>.admin__control-select,.admin__control-table th>.admin__control-text{width:100%}.admin__control-table td._hidden,.admin__control-table th._hidden{display:none}.admin__control-table td._fit,.admin__control-table th._fit{width:1px}.admin__control-table th{color:#303030;font-size:1.4rem;font-weight:600;vertical-align:bottom}.admin__control-table th._required span:after{color:#eb5202;content:'*'}.admin__control-table .control-table-actions-th{white-space:nowrap}.admin__control-table .control-table-actions-cell{padding-top:1.8rem;text-align:center;width:1%}.admin__control-table .control-table-options-th{text-align:center;width:10rem}.admin__control-table .control-table-options-cell{text-align:center}.admin__control-table .control-table-text{line-height:3.2rem}.admin__control-table .col-draggable{padding-top:2.2rem;width:1%}.admin__control-table .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.admin__control-table .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-table .action-delete:before{content:'\e630';font-size:2rem}.admin__control-table .action-delete>span{display:none}.admin__control-table .draggable-handle{padding:0}.admin__control-table._dragged{outline:#007bdb solid 1px}.admin__control-table-action{background-color:#efefef;border-top:1px solid #fff;padding:1.3rem 1rem}.admin__dynamic-rows._dragged{opacity:.95;position:absolute;z-index:999}.admin__dynamic-rows.admin__control-table .admin__control-fields>.admin__field{border:0;padding:0}.admin__dynamic-rows td>.admin__field{border:0;margin:0;padding:0}.admin__control-table-pagination{padding-bottom:1rem}.admin__control-table-pagination .admin__data-grid-pager{float:right}.admin__field-tooltip{display:inline-block;margin-top:.5rem;max-width:45px;overflow:visible;vertical-align:top;width:0}.admin__field-tooltip:hover{position:relative;z-index:500}.admin__field-option .admin__field-tooltip{margin-top:.5rem}.admin__field-tooltip .admin__field-tooltip-action{margin-left:2rem;position:relative;z-index:2;display:inline-block;text-decoration:none}.admin__field-tooltip .admin__field-tooltip-action:before{-webkit-font-smoothing:antialiased;font-size:2.2rem;line-height:1;color:#514943;content:'\e633';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.admin__field-tooltip .admin__control-text:focus+.admin__field-tooltip-content,.admin__field-tooltip:hover .admin__field-tooltip-content{display:block}.admin__field-tooltip .admin__field-tooltip-content{bottom:3.8rem;display:none;right:-2.3rem}.admin__field-tooltip .admin__field-tooltip-content:after,.admin__field-tooltip .admin__field-tooltip-content:before{border:1.6rem solid transparent;height:0;width:0;border-top-color:#afadac;content:'';display:block;position:absolute;right:2rem;top:100%;z-index:3}.admin__field-tooltip .admin__field-tooltip-content:after{border-top-color:#fffbbb;margin-top:-1px;z-index:4}.abs-admin__field-tooltip-content,.admin__field-tooltip .admin__field-tooltip-content{box-shadow:0 2px 8px 0 rgba(0,0,0,.3);background:#fffbbb;border:1px solid #afadac;border-radius:1px;padding:1.5rem 2.5rem;position:absolute;width:32rem;z-index:1}.admin__field-fallback-reset{font-size:1.25rem;white-space:nowrap;width:30px}.admin__field-fallback-reset>span{margin-left:.5rem;position:relative}.admin__field-fallback-reset:active{-ms-transform:scale(0.98);transform:scale(0.98)}.admin__field-fallback-reset:before{transition:color .1s linear;content:'\e642';font-size:1.3rem;margin-left:.5rem}.admin__field-fallback-reset:hover{cursor:pointer;text-decoration:none}.admin__field-fallback-reset:focus{background:0 0}.abs-field-size-x-small,.abs-field-sizes.admin__field-x-small>.admin__field-control,.admin__field.admin__field-x-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-x-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-x-small>.admin__field-control{width:8rem}.abs-field-size-small,.abs-field-sizes.admin__field-small>.admin__field-control,.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control,.admin__field.admin__field-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-small>.admin__field-control{width:15rem}.abs-field-size-medium,.abs-field-sizes.admin__field-medium>.admin__field-control,.admin__field.admin__field-medium>.admin__field-control,.admin__fieldset>.admin__field.admin__field-medium>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-medium>.admin__field-control{width:34rem}.abs-field-size-large,.abs-field-sizes.admin__field-large>.admin__field-control,.admin__field.admin__field-large>.admin__field-control,.admin__fieldset>.admin__field.admin__field-large>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-large>.admin__field-control{width:64rem}.abs-field-no-label,.admin__field-group-additional,.admin__field-no-label,.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-control{margin-left:calc((100%) * .25 + 30px)}.admin__fieldset{border:0;margin:0;min-width:0;padding:0}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title{padding-left:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title strong{font-size:1.7rem;font-weight:600}.admin__fieldset .fieldset-wrapper.admin__fieldset-section .admin__fieldset-wrapper-content>.admin__fieldset{padding-top:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section:last-child .admin__fieldset-wrapper-content>.admin__fieldset{padding-bottom:0}.admin__fieldset>.admin__field{border:0;margin:0 0 0 -30px;padding:0}.admin__fieldset>.admin__field:after{clear:both;content:'';display:table}.admin__fieldset>.admin__field>.admin__field-control{width:calc((100%) * .5 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-label{display:none}.admin__fieldset>.admin__field+.admin__field._empty._no-header{margin-top:-3rem}.admin__fieldset-product-websites{position:relative;z-index:300}.admin__fieldset-note{margin-bottom:2rem}.admin__form-field{border:0;margin:0;padding:0}.admin__field-control .admin__control-text,.admin__field-control .admin__control-textarea,.admin__form-field-control .admin__control-text,.admin__form-field-control .admin__control-textarea{width:100%}.admin__field-label{color:#303030;cursor:pointer;margin:0;text-align:right}.admin__field-label+br{display:none}.admin__field:not(.admin__field-option)>.admin__field-label{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:3.2rem;padding:0;white-space:nowrap}.admin__field:not(.admin__field-option)>.admin__field-label:before{opacity:0;visibility:hidden;content:'.';margin-left:-7px;overflow:hidden}.admin__field:not(.admin__field-option)>.admin__field-label span{display:inline-block;line-height:1.2;vertical-align:middle;white-space:normal}.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]{position:relative}._required>.admin__field-label>span:after,.required>.admin__field-label>span:after{color:#eb5202;content:'*';display:inline-block;font-size:1.6rem;font-weight:500;line-height:1;margin-left:10px;margin-top:.2rem;position:absolute;z-index:1}._disabled>.admin__field-label{color:#999;cursor:default}.admin__field{margin-bottom:0}.admin__field+.admin__field{margin-top:1.5rem}.admin__field:not(.admin__field-option)~.admin__field-option{margin-top:.5rem}.admin__field.admin__field-option~.admin__field-option{margin-top:.9rem}.admin__field~.admin__field-option:last-child{margin-bottom:.8rem}.admin__fieldset>.admin__field{margin-bottom:3rem;position:relative}.admin__field legend.admin__field-label{opacity:0}.admin__field[data-config-scope]:before{color:gray;content:attr(data-config-scope);display:inline-block;font-size:1.2rem;left:calc((100%) * .75 - 30px);line-height:3.2rem;margin-left:60px;position:absolute;width:calc((100%) * .25 - 30px)}.admin__field-control .admin__field[data-config-scope]:nth-child(n+2):before{content:''}.admin__field._error .admin__field-control [class*=admin__addon-]:before,.admin__field._error .admin__field-control [class*=admin__control-] [class*=admin__addon-]:before,.admin__field._error .admin__field-control>[class*=admin__control-]{border-color:#e22626}.admin__field._disabled,.admin__field._disabled:hover{box-shadow:inherit;cursor:inherit;opacity:1;outline:inherit}.admin__field._hidden{display:none}.admin__field-control+.admin__field-control{margin-top:1.5rem}.admin__field-control._with-tooltip>.admin__control-addon,.admin__field-control._with-tooltip>.admin__control-select,.admin__field-control._with-tooltip>.admin__control-text,.admin__field-control._with-tooltip>.admin__control-textarea,.admin__field-control._with-tooltip>.admin__field-option{max-width:calc(100% - 45px - 4px)}.admin__field-control._with-tooltip .admin__field-tooltip{width:auto}.admin__field-control._with-tooltip .admin__field-option{display:inline-block}.admin__field-control._with-reset>.admin__control-addon,.admin__field-control._with-reset>.admin__control-text,.admin__field-control._with-reset>.admin__control-textarea{width:calc(100% - 30px - .5rem - 4px)}.admin__field-control._with-reset .admin__field-fallback-reset{margin-left:.5rem;margin-top:1rem;vertical-align:top}.admin__field-control._with-reset._with-tooltip>.admin__control-addon,.admin__field-control._with-reset._with-tooltip>.admin__control-text,.admin__field-control._with-reset._with-tooltip>.admin__control-textarea{width:calc(100% - 30px - .5rem - 45px - 8px)}.admin__fieldset>.admin__field-collapsible{margin-bottom:0}.admin__fieldset>.admin__field-collapsible .admin__field-control{border-top:1px solid #ccc;display:block;font-size:1.7rem;font-weight:700;padding:1.7rem 0;width:calc(97%)}.admin__fieldset>.admin__field-collapsible .admin__field-option{padding-top:0}.admin__field-collapsible+div{margin-top:2.5rem}.admin__field-collapsible .admin__control-radio+label:before{height:1.8rem;width:1.8rem}.admin__field-collapsible .admin__control-radio:checked+label:after{left:4px;top:5px}.admin__field-error{background:#fffbbb;border:1px solid #ee7d7d;box-sizing:border-box;color:#555;display:block;font-size:1.2rem;font-weight:400;line-height:1.2;margin:.2rem 0 0;padding:.8rem 1rem .9rem}.admin__field-note{color:#303030;font-size:1.2rem;margin:10px 0 0;padding:0}.admin__additional-info{padding-top:1rem}.admin__field-option{padding-top:.7rem}.admin__field-option .admin__field-label{text-align:left}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2),.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1){display:inline-block}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option{display:inline-block;margin-left:41px;margin-top:0}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option:before,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option:before{background:#cacaca;content:'';display:inline-block;height:20px;margin-left:-20px;position:absolute;width:1px}.admin__field-value{display:inline-block;padding-top:.7rem}.admin__field-service{padding-top:1rem}.admin__control-fields>.admin__field:first-child,[class*=admin__control-grouped]>.admin__field:first-child{position:static}.admin__control-fields>.admin__field:first-child>.admin__field-label,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px;background:#fff;cursor:pointer;left:0;position:absolute;top:0}.admin__control-fields>.admin__field:first-child>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label span:before{display:block}.admin__control-fields>.admin__field._disabled>.admin__field-label,[class*=admin__control-grouped]>.admin__field._disabled>.admin__field-label{cursor:default}.admin__control-fields>.admin__field>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field>.admin__field-label span:before{display:none}.admin__control-fields .admin__field-label~.admin__field-control{width:100%}.admin__control-fields .admin__field-option{padding-top:0}[class*=admin__control-grouped]{box-sizing:border-box;display:table;width:100%}[class*=admin__control-grouped]>.admin__field{display:table-cell;vertical-align:top}[class*=admin__control-grouped]>.admin__field>.admin__field-control{float:none;width:100%}[class*=admin__control-grouped]>.admin__field.admin__field-default,[class*=admin__control-grouped]>.admin__field.admin__field-large,[class*=admin__control-grouped]>.admin__field.admin__field-medium,[class*=admin__control-grouped]>.admin__field.admin__field-small,[class*=admin__control-grouped]>.admin__field.admin__field-x-small{width:1px}[class*=admin__control-grouped]>.admin__field.admin__field-default+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-large+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-medium+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-small+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-x-small+.admin__field:last-child{width:auto}[class*=admin__control-grouped]>.admin__field:nth-child(n+2){padding-left:20px}.admin__control-group-equal{table-layout:fixed}.admin__control-group-equal>.admin__field{width:50%}.admin__field-control-group{margin-top:.8rem}.admin__field-control-group>.admin__field{padding:0}.admin__control-grouped-date>.admin__field-date{white-space:nowrap;width:1px}.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control{float:left;position:relative}.admin__control-grouped-date>.admin__field-date+.admin__field:last-child{width:auto}.admin__control-grouped-date>.admin__field-date+.admin__field-date>.admin__field-label{float:left;padding-right:20px}.admin__control-grouped-date .ui-datepicker-trigger{left:100%;top:0}.admin__field-group-columns.admin__field-control.admin__control-grouped{width:calc((100%) * 1 - 30px);float:left;margin-left:30px}.admin__field-group-columns>.admin__field:first-child>.admin__field-label{float:none;margin:0;opacity:1;position:static;text-align:left}.admin__field-group-columns .admin__control-select{width:100%}.admin__field-group-additional{clear:both}.admin__field-group-additional .action-advanced{margin-top:1rem}.admin__field-group-additional .action-secondary{width:100%}.admin__field-group-show-label{white-space:nowrap}.admin__field-group-show-label>.admin__field-control,.admin__field-group-show-label>.admin__field-label{display:inline-block;vertical-align:top}.admin__field-group-show-label>.admin__field-label{margin-right:20px}.admin__field-complex{margin:1rem 0 3rem;padding-left:1rem}.admin__field:not(._hidden)+.admin__field-complex{margin-top:3rem}.admin__field-complex .admin__field-complex-title{clear:both;color:#303030;font-size:1.7rem;font-weight:600;letter-spacing:.025em;margin-bottom:1rem}.admin__field-complex .admin__field-complex-elements{float:right;max-width:40%}.admin__field-complex .admin__field-complex-elements button{margin-left:1rem}.admin__field-complex .admin__field-complex-content{max-width:60%;overflow:hidden}.admin__field-complex .admin__field-complex-text{margin-left:-1rem}.admin__field-complex+.admin__field._empty._no-header{margin-top:-3rem}.admin__legend{float:left;position:static;width:100%}.admin__legend+br{clear:left;display:block;height:0;overflow:hidden}.message{margin-bottom:3rem}.message-icon-top:before{margin-top:0;top:1.8rem}.nav{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;display:none;margin-bottom:3rem;padding:2.2rem 1.5rem 0 0}.nav .btn-group,.nav-bar-outer-actions{float:right;margin-bottom:1.7rem}.nav .btn-group .btn-wrap,.nav-bar-outer-actions .btn-wrap{float:right;margin-left:.5rem;margin-right:.5rem}.nav .btn-group .btn-wrap .btn,.nav-bar-outer-actions .btn-wrap .btn{padding-left:.5rem;padding-right:.5rem}.nav-bar-outer-actions{margin-top:-10.6rem;padding-right:1.5rem}.btn-wrap-try-again{width:9.5rem}.btn-wrap-next,.btn-wrap-prev{width:8.5rem}.nav-bar{counter-reset:i;float:left;margin:0 1rem 1.7rem 0;padding:0;position:relative;white-space:nowrap}.nav-bar:before{background-color:#d4d4d4;background-repeat:repeat-x;background-image:linear-gradient(to bottom,#d1d1d1 0,#d4d4d4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#d1d1d1', endColorstr='#d4d4d4', GradientType=0);border-bottom:1px solid #d9d9d9;border-top:1px solid #bfbfbf;content:'';height:1rem;left:5.15rem;position:absolute;right:5.15rem;top:.7rem}.nav-bar>li{display:inline-block;font-size:0;position:relative;vertical-align:top;width:10.3rem}.nav-bar>li:first-child:after{display:none}.nav-bar>li:after{background-color:#514943;content:'';height:.5rem;left:calc(-50% + .25rem);position:absolute;right:calc(50% + .7rem);top:.9rem}.nav-bar>li.disabled:before,.nav-bar>li.ui-state-disabled:before{bottom:0;content:'';left:0;position:absolute;right:0;top:0;z-index:1}.nav-bar>li.active~li:after,.nav-bar>li.ui-state-active~li:after{display:none}.nav-bar>li.active~li a:after,.nav-bar>li.ui-state-active~li a:after{background-color:transparent;border-color:transparent;color:#a6a6a6}.nav-bar>li.active a,.nav-bar>li.ui-state-active a{color:#000}.nav-bar>li.active a:hover,.nav-bar>li.ui-state-active a:hover{cursor:default}.nav-bar>li.active a:after,.nav-bar>li.ui-state-active a:after{background-color:#fff;content:''}.nav-bar a{color:#514943;display:block;font-size:1.2rem;font-weight:600;line-height:1.2;overflow:hidden;padding:3rem .5em 0;position:relative;text-align:center;text-overflow:ellipsis}.nav-bar a:hover{text-decoration:none}.nav-bar a:after{background-color:#514943;border:.4rem solid #514943;border-radius:100%;color:#fff;content:counter(i);counter-increment:i;height:1.5rem;left:50%;line-height:.6;margin-left:-.8rem;position:absolute;right:auto;text-align:center;top:.4rem;width:1.5rem}.nav-bar a:before{background-color:#d6d6d6;border:1px solid transparent;border-bottom-color:#d9d9d9;border-radius:100%;border-top-color:#bfbfbf;content:'';height:2.3rem;left:50%;line-height:1;margin-left:-1.2rem;position:absolute;top:0;width:2.3rem}.tooltip{display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.19rem;font-weight:400;line-height:1.4;opacity:0;position:absolute;visibility:visible;z-index:10}.tooltip.in{opacity:.9}.tooltip.top{margin-top:-4px;padding:8px 0}.tooltip.right{margin-left:4px;padding:0 8px}.tooltip.bottom{margin-top:4px;padding:8px 0}.tooltip.left{margin-left:-4px;padding:0 8px}.tooltip p:last-child{margin-bottom:0}.tooltip-inner{background-color:#fff;border:1px solid #adadad;border-radius:0;box-shadow:1px 1px 1px #ccc;color:#41362f;max-width:31rem;padding:.5em 1em;text-decoration:none}.tooltip-arrow,.tooltip-arrow:after{border:solid transparent;height:0;position:absolute;width:0}.tooltip-arrow:after{content:'';position:absolute}.tooltip.top .tooltip-arrow,.tooltip.top .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:50%;margin-left:-8px}.tooltip.top-left .tooltip-arrow,.tooltip.top-left .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;margin-bottom:-8px;right:8px}.tooltip.top-right .tooltip-arrow,.tooltip.top-right .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:8px;margin-bottom:-8px}.tooltip.right .tooltip-arrow,.tooltip.right .tooltip-arrow:after{border-right-color:#949494;border-width:8px 8px 8px 0;left:1px;margin-top:-8px;top:50%}.tooltip.right .tooltip-arrow:after{border-right-color:#fff;border-width:6px 7px 6px 0;margin-left:0;margin-top:-6px}.tooltip.left .tooltip-arrow,.tooltip.left .tooltip-arrow:after{border-left-color:#949494;border-width:8px 0 8px 8px;margin-top:-8px;right:0;top:50%}.tooltip.bottom .tooltip-arrow,.tooltip.bottom .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:50%;margin-left:-8px;top:0}.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;margin-top:-8px;right:8px;top:0}.tooltip.bottom-right .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:8px;margin-top:-8px;top:0}.password-strength{display:block;margin:0 -.3rem 1em;white-space:nowrap}.password-strength.password-strength-too-short .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child+.password-strength-item{background-color:#e22626}.password-strength.password-strength-fair .password-strength-item:first-child,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item+.password-strength-item{background-color:#ef672f}.password-strength.password-strength-good .password-strength-item:first-child,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item+.password-strength-item,.password-strength.password-strength-strong .password-strength-item{background-color:#79a22e}.password-strength .password-strength-item{background-color:#ccc;display:inline-block;font-size:0;height:1.4rem;margin-right:.3rem;width:calc(20% - .6rem)}@keyframes progress-bar-stripes{from{background-position:4rem 0}to{background-position:0 0}}.progress{background-color:#fafafa;border:1px solid #ccc;clear:left;height:3rem;margin-bottom:3rem;overflow:hidden}.progress-bar{background-color:#79a22e;color:#fff;float:left;font-size:1.19rem;height:100%;line-height:3rem;text-align:center;transition:width .6s ease;width:0}.progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.progress-bar-text-description{margin-bottom:1.6rem}.progress-bar-text-progress{text-align:right}.page-columns .page-inner-sidebar{margin:0 0 3rem}.page-header{margin-bottom:2.7rem;padding-bottom:2rem;position:relative}.page-header:before{border-bottom:1px solid #e3e3e3;bottom:0;content:'';display:block;height:1px;left:3rem;position:absolute;right:3rem}.container .page-header:before{content:normal}.page-header .message{margin-bottom:1.8rem}.page-header .message+.message{margin-top:-1.5rem}.page-header .admin__action-dropdown,.page-header .search-global-input{transition:none}.container .page-header{margin-bottom:0}.page-title-wrapper{margin-top:1.1rem}.container .page-title-wrapper{background:url(../../pub/images/logo.svg) no-repeat;min-height:41px;padding:4px 0 0 45px}.admin__menu .level-0:first-child>a{margin-top:1.6rem}.admin__menu .level-0:first-child>a:after{top:-1.6rem}.admin__menu .level-0:first-child._active>a:after{display:block}.admin__menu .level-0>a{padding-bottom:1.3rem;padding-top:1.3rem}.admin__menu .level-0>a:before{margin-bottom:.7rem}.admin__menu .item-home>a:before{content:'\e611';font-size:2.3rem;padding-top:-.1rem}.admin__menu .item-component>a:before{content:'\e612'}.admin__menu .item-extension>a:before{content:'\e612'}.admin__menu .item-module>a:before{content:'\e647'}.admin__menu .item-upgrade>a:before{content:'\e614'}.admin__menu .item-system-config>a:before{content:'\e610'}.admin__menu .item-tools>a:before{content:'\e613'}.modal-sub-title{font-size:1.7rem;font-weight:600}.modal-connect-signin .modal-inner-wrap{max-width:80rem}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{-webkit-overflow-scrolling:touch;bottom:0;box-sizing:border-box;left:0;overflow:auto;position:fixed;right:0;top:0;z-index:999}.ngdialog *,.ngdialog:after,.ngdialog:before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation *{animation:none!important}.ngdialog.ngdialog-closing .ngdialog-content,.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-animation:ngdialog-fadeout .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadeout .5s}.ngdialog-overlay{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s;background:rgba(0,0,0,.4);bottom:0;left:0;position:fixed;right:0;top:0}.ngdialog-content{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s}body.ngdialog-open{overflow:hidden}.component-indicator{border-radius:50%;cursor:help;display:inline-block;height:16px;text-align:center;vertical-align:middle;width:16px}.component-indicator::after,.component-indicator::before{background:#fff;display:block;opacity:0;position:absolute;transition:opacity .2s linear .1s;visibility:hidden}.component-indicator::before{border:1px solid #adadad;border-radius:1px;box-shadow:0 0 2px rgba(0,0,0,.4);content:attr(data-label);font-size:1.2rem;margin:30px 0 0 -10px;min-width:50px;padding:4px 5px}.component-indicator::after{border-color:#999;border-style:solid;border-width:1px 0 0 1px;box-shadow:-1px -1px 1px rgba(0,0,0,.1);content:'';height:10px;margin:9px 0 0 5px;-ms-transform:rotate(45deg);transform:rotate(45deg);width:10px}.component-indicator:hover::after,.component-indicator:hover::before{opacity:1;transition:opacity .2s linear;visibility:visible}.component-indicator span{display:block;height:16px;overflow:hidden;width:16px}.component-indicator span:before{content:'';display:block;font-family:Icons;font-size:16px;height:100%;line-height:16px;width:100%}.component-indicator._on{background:#79a22e}.component-indicator._off{background:#e22626}.component-indicator._off span:before{background:#fff;height:4px;margin:8px auto 20px;width:12px}.component-indicator._info{background:0 0}.component-indicator._info span{width:21px}.component-indicator._info span:before{color:#008bdb;content:'\e648';font-family:Icons;font-size:16px}.component-indicator._tooltip{background:0 0;margin:0 0 8px 5px}.component-indicator._tooltip a{width:21px}.component-indicator._tooltip a:hover{text-decoration:none}.component-indicator._tooltip a:before{color:#514943;content:'\e633';font-family:Icons;font-size:16px}.col-manager-item-name .data-grid-data{padding-left:5px}.col-manager-item-name .ng-hide+.data-grid-data{padding-left:24px}.col-manager-item-name ._hide-dependencies,.col-manager-item-name ._show-dependencies{cursor:pointer;padding-left:24px;position:relative}.col-manager-item-name ._hide-dependencies:before,.col-manager-item-name ._show-dependencies:before{display:block;font-family:Icons;font-size:12px;left:0;position:absolute;top:1px}.col-manager-item-name ._show-dependencies:before{content:'\e62b'}.col-manager-item-name ._hide-dependencies:before{content:'\e628'}.col-manager-item-name ._no-dependencies{padding-left:24px}.product-modules-block{font-size:1.2rem;padding:15px 0 0}.col-manager-item-name .product-modules-block{padding-left:1rem}.product-modules-descriprion,.product-modules-title{font-weight:700;margin:0 0 7px}.product-modules-list{font-size:1.1rem;list-style:none;margin:0}.col-manager-item-name .product-modules-list{margin-left:15px}.col-manager-item-name .product-modules-list li{padding:0 0 0 15px;position:relative}.product-modules-list li{margin:0 0 .5rem}.product-modules-list .component-indicator{height:10px;left:0;position:absolute;top:3px;width:10px}.module-summary{white-space:nowrap}.module-summary-title{font-size:2.1rem;margin-right:1rem}.app-updater .nav{display:block;margin-bottom:3.1rem;margin-top:-2.8rem}.app-updater .nav-bar-outer-actions{margin-top:1rem;padding-right:0}.app-updater .nav-bar-outer-actions .btn-wrap-cancel{margin-right:2.6rem}.main{padding-bottom:2rem;padding-top:3rem}.menu-wrapper .logo-static{pointer-events:none}.header{display:none}.header .logo{float:left;height:4.1rem;width:3.5rem}.header-title{font-size:2.8rem;letter-spacing:.02em;line-height:1.4;margin:2.5rem 0 3.5rem 5rem}.page-title{margin-bottom:1rem}.page-sub-title{font-size:2rem}.accent-box{margin-bottom:2rem}.accent-box .btn-prime{margin-top:1.5rem}.spinner.side{float:left;font-size:2.4rem;margin-left:2rem;margin-top:-5px}.page-landing{margin:7.6% auto 0;max-width:44rem;text-align:center}.page-landing .logo{height:5.6rem;margin-bottom:2rem;width:19.2rem}.page-landing .text-version{margin-bottom:3rem}.page-landing .text-welcome{margin-bottom:6.5rem}.page-landing .text-terms{margin-bottom:2.5rem;text-align:center}.page-landing .btn-submit,.page-license .license-text{margin-bottom:2rem}.page-license .page-license-footer{text-align:right}.readiness-check-item{margin-bottom:4rem;min-height:2.5rem}.readiness-check-item .spinner{float:left;font-size:2.5rem;margin:-.4rem 0 0 1.7rem}.readiness-check-title{font-size:1.4rem;font-weight:700;margin-bottom:.1rem;margin-left:5.7rem}.readiness-check-content{margin-left:5.7rem;margin-right:22rem;position:relative}.readiness-check-content .readiness-check-title{margin-left:0}.readiness-check-content .list{margin-top:-.3rem}.readiness-check-side{left:100%;padding-left:2.4rem;position:absolute;top:0;width:22rem}.readiness-check-side .side-title{margin-bottom:0}.readiness-check-icon{float:left;margin-left:1.7rem;margin-top:.3rem}.extensions-information{margin-bottom:5rem}.extensions-information h3{font-size:1.4rem;margin-bottom:1.3rem}.extensions-information .message{margin-bottom:2.5rem}.extensions-information .message:before{margin-top:0;top:1.8rem}.extensions-information .extensions-container{padding:0 2rem}.extensions-information .list{margin-bottom:1rem}.extensions-information .list select{cursor:pointer}.extensions-information .list select:disabled{background:#ccc;cursor:default}.extensions-information .list .extension-delete{font-size:1.7rem;padding-top:0}.delete-modal-wrap{padding:0 4% 4rem}.delete-modal-wrap h3{font-size:3.4rem;display:inline-block;font-weight:300;margin:0 0 2rem;padding:.9rem 0 0;vertical-align:top}.delete-modal-wrap .actions{padding:3rem 0 0}.page-web-configuration .form-el-insider-wrap{width:auto}.page-web-configuration .form-el-insider{width:15.4rem}.page-web-configuration .form-el-insider-input .form-el-input{width:16.5rem}.customize-your-store .advanced-modules-count,.customize-your-store .advanced-modules-select{padding-left:1.5rem}.customize-your-store .customize-your-store-advanced{min-width:0}.customize-your-store .message-error:before{margin-top:0;top:1.8rem}.customize-your-store .message-error a{color:#333;text-decoration:underline}.customize-your-store .message-error .form-label:before{background:#fff}.customize-your-store .customize-database-clean p{margin-top:2.5rem}.content-install{margin-bottom:2rem}.console{border:1px solid #ccc;font-family:'Courier New',Courier,monospace;font-weight:300;height:20rem;margin:1rem 0 2rem;overflow-y:auto;padding:1.5rem 2rem 2rem;resize:vertical}.console .text-danger{color:#e22626}.console .text-success{color:#090}.console .hidden{display:none}.content-success .btn-prime{margin-top:1.5rem}.jumbo-title{font-size:3.6rem}.jumbo-title .jumbo-icon{font-size:3.8rem;margin-right:.25em;position:relative;top:.15em}.install-database-clean{margin-top:4rem}.install-database-clean .btn{margin-right:1rem}.page-sub-title{margin-bottom:2.1rem;margin-top:3rem}.multiselect-custom{max-width:71.1rem}.content-install{margin-top:3.7rem}.home-page-inner-wrap{margin:0 auto;max-width:91rem}.setup-home-title{margin-bottom:3.9rem;padding-top:1.8rem;text-align:center}.setup-home-item{background-color:#fafafa;border:1px solid #ccc;color:#333;display:block;margin-bottom:2rem;margin-left:1.3rem;margin-right:1.3rem;min-height:30rem;padding:2rem;text-align:center}.setup-home-item:hover{border-color:#8c8c8c;color:#333;text-decoration:none;transition:border-color .1s linear}.setup-home-item:active{-ms-transform:scale(0.99);transform:scale(0.99)}.setup-home-item:before{display:block;font-size:7rem;margin-bottom:3.3rem;margin-top:4rem}.setup-home-item-component:before,.setup-home-item-extension:before{content:'\e612'}.setup-home-item-module:before{content:'\e647'}.setup-home-item-upgrade:before{content:'\e614'}.setup-home-item-configuration:before{content:'\e610'}.setup-home-item-title{display:block;font-size:1.8rem;letter-spacing:.025em;margin-bottom:1rem}.setup-home-item-description{display:block}.extension-manager-wrap{border:1px solid #bbb;margin:0 0 4rem}.extension-manager-account{font-size:2.1rem;display:inline-block;font-weight:400}.extension-manager-title{font-size:3.2rem;background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;color:#41362f;font-weight:600;line-height:1.2;padding:2rem}.extension-manager-content{padding:2.5rem 2rem 2rem}.extension-manager-items{list-style:none;margin:0;text-align:center}.extension-manager-items .btn{border:1px solid #adadad;display:block;margin:1rem auto 0}.extension-manager-items .item-title{font-size:2.1rem;display:inline-block;text-align:left}.extension-manager-items .item-number{font-size:4.1rem;display:inline-block;line-height:.8;margin:0 5px 1.5rem 0;vertical-align:top}.extension-manager-items .item-date{font-size:2.6rem;margin-top:1px}.extension-manager-items .item-date-title{font-size:1.5rem}.extension-manager-items .item-install{margin:0 0 2rem}.sync-login-wrap{padding:0 10% 4rem}.sync-login-wrap .legend{font-size:2.6rem;color:#eb5202;float:left;font-weight:300;line-height:1.2;margin:-1rem 0 2.5rem;position:static;width:100%}.sync-login-wrap .legend._hidden{display:none}.sync-login-wrap .login-header{font-size:3.4rem;font-weight:300;margin:0 0 2rem}.sync-login-wrap .login-header span{display:inline-block;padding:.9rem 0 0;vertical-align:top}.sync-login-wrap h4{font-size:1.4rem;margin:0 0 2rem}.sync-login-wrap .sync-login-steps{margin:0 0 2rem 1.5rem}.sync-login-wrap .sync-login-steps li{padding:0 0 0 1rem}.sync-login-wrap .form-row .form-label{display:inline-block}.sync-login-wrap .form-row .form-label.required{padding-left:1.5rem}.sync-login-wrap .form-row .form-label.required:after{left:0;position:absolute;right:auto}.sync-login-wrap .form-row{max-width:28rem}.sync-login-wrap .form-actions{display:table;margin-top:-1.3rem}.sync-login-wrap .form-actions .links{display:table-header-group}.sync-login-wrap .form-actions .actions{padding:3rem 0 0}@media all and (max-width:1047px){.admin__menu .submenu li{min-width:19.8rem}.nav{padding-bottom:5.38rem;padding-left:1.5rem;text-align:center}.nav-bar{display:inline-block;float:none;margin-right:0;vertical-align:top}.nav .btn-group,.nav-bar-outer-actions{display:inline-block;float:none;margin-top:-8.48rem;text-align:center;vertical-align:top;width:100%}.nav-bar-outer-actions{padding-right:0}.nav-bar-outer-actions .outer-actions-inner-wrap{display:inline-block}.app-updater .nav{padding-bottom:1.7rem}.app-updater .nav-bar-outer-actions{margin-top:2rem}}@media all and (min-width:768px){.page-layout-admin-2columns-left .page-columns{margin-left:-30px}.page-layout-admin-2columns-left .page-columns:after{clear:both;content:'';display:table}.page-layout-admin-2columns-left .page-columns .main-col{width:calc((100%) * .75 - 30px);float:right}.page-layout-admin-2columns-left .page-columns .side-col{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9{float:left}.col-m-12{width:100%}.col-m-11{width:91.66666667%}.col-m-10{width:83.33333333%}.col-m-9{width:75%}.col-m-8{width:66.66666667%}.col-m-7{width:58.33333333%}.col-m-6{width:50%}.col-m-5{width:41.66666667%}.col-m-4{width:33.33333333%}.col-m-3{width:25%}.col-m-2{width:16.66666667%}.col-m-1{width:8.33333333%}.col-m-pull-12{right:100%}.col-m-pull-11{right:91.66666667%}.col-m-pull-10{right:83.33333333%}.col-m-pull-9{right:75%}.col-m-pull-8{right:66.66666667%}.col-m-pull-7{right:58.33333333%}.col-m-pull-6{right:50%}.col-m-pull-5{right:41.66666667%}.col-m-pull-4{right:33.33333333%}.col-m-pull-3{right:25%}.col-m-pull-2{right:16.66666667%}.col-m-pull-1{right:8.33333333%}.col-m-pull-0{right:auto}.col-m-push-12{left:100%}.col-m-push-11{left:91.66666667%}.col-m-push-10{left:83.33333333%}.col-m-push-9{left:75%}.col-m-push-8{left:66.66666667%}.col-m-push-7{left:58.33333333%}.col-m-push-6{left:50%}.col-m-push-5{left:41.66666667%}.col-m-push-4{left:33.33333333%}.col-m-push-3{left:25%}.col-m-push-2{left:16.66666667%}.col-m-push-1{left:8.33333333%}.col-m-push-0{left:auto}.col-m-offset-12{margin-left:100%}.col-m-offset-11{margin-left:91.66666667%}.col-m-offset-10{margin-left:83.33333333%}.col-m-offset-9{margin-left:75%}.col-m-offset-8{margin-left:66.66666667%}.col-m-offset-7{margin-left:58.33333333%}.col-m-offset-6{margin-left:50%}.col-m-offset-5{margin-left:41.66666667%}.col-m-offset-4{margin-left:33.33333333%}.col-m-offset-3{margin-left:25%}.col-m-offset-2{margin-left:16.66666667%}.col-m-offset-1{margin-left:8.33333333%}.col-m-offset-0{margin-left:0}.page-columns{margin-left:-30px}.page-columns:after{clear:both;content:'';display:table}.page-columns .page-inner-content{width:calc((100%) * .75 - 30px);float:right}.page-columns .page-inner-sidebar{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}}@media all and (min-width:1048px){.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9{float:left}.col-l-12{width:100%}.col-l-11{width:91.66666667%}.col-l-10{width:83.33333333%}.col-l-9{width:75%}.col-l-8{width:66.66666667%}.col-l-7{width:58.33333333%}.col-l-6{width:50%}.col-l-5{width:41.66666667%}.col-l-4{width:33.33333333%}.col-l-3{width:25%}.col-l-2{width:16.66666667%}.col-l-1{width:8.33333333%}.col-l-pull-12{right:100%}.col-l-pull-11{right:91.66666667%}.col-l-pull-10{right:83.33333333%}.col-l-pull-9{right:75%}.col-l-pull-8{right:66.66666667%}.col-l-pull-7{right:58.33333333%}.col-l-pull-6{right:50%}.col-l-pull-5{right:41.66666667%}.col-l-pull-4{right:33.33333333%}.col-l-pull-3{right:25%}.col-l-pull-2{right:16.66666667%}.col-l-pull-1{right:8.33333333%}.col-l-pull-0{right:auto}.col-l-push-12{left:100%}.col-l-push-11{left:91.66666667%}.col-l-push-10{left:83.33333333%}.col-l-push-9{left:75%}.col-l-push-8{left:66.66666667%}.col-l-push-7{left:58.33333333%}.col-l-push-6{left:50%}.col-l-push-5{left:41.66666667%}.col-l-push-4{left:33.33333333%}.col-l-push-3{left:25%}.col-l-push-2{left:16.66666667%}.col-l-push-1{left:8.33333333%}.col-l-push-0{left:auto}.col-l-offset-12{margin-left:100%}.col-l-offset-11{margin-left:91.66666667%}.col-l-offset-10{margin-left:83.33333333%}.col-l-offset-9{margin-left:75%}.col-l-offset-8{margin-left:66.66666667%}.col-l-offset-7{margin-left:58.33333333%}.col-l-offset-6{margin-left:50%}.col-l-offset-5{margin-left:41.66666667%}.col-l-offset-4{margin-left:33.33333333%}.col-l-offset-3{margin-left:25%}.col-l-offset-2{margin-left:16.66666667%}.col-l-offset-1{margin-left:8.33333333%}.col-l-offset-0{margin-left:0}}@media all and (min-width:1440px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-pull-12{right:100%}.col-xl-pull-11{right:91.66666667%}.col-xl-pull-10{right:83.33333333%}.col-xl-pull-9{right:75%}.col-xl-pull-8{right:66.66666667%}.col-xl-pull-7{right:58.33333333%}.col-xl-pull-6{right:50%}.col-xl-pull-5{right:41.66666667%}.col-xl-pull-4{right:33.33333333%}.col-xl-pull-3{right:25%}.col-xl-pull-2{right:16.66666667%}.col-xl-pull-1{right:8.33333333%}.col-xl-pull-0{right:auto}.col-xl-push-12{left:100%}.col-xl-push-11{left:91.66666667%}.col-xl-push-10{left:83.33333333%}.col-xl-push-9{left:75%}.col-xl-push-8{left:66.66666667%}.col-xl-push-7{left:58.33333333%}.col-xl-push-6{left:50%}.col-xl-push-5{left:41.66666667%}.col-xl-push-4{left:33.33333333%}.col-xl-push-3{left:25%}.col-xl-push-2{left:16.66666667%}.col-xl-push-1{left:8.33333333%}.col-xl-push-0{left:auto}.col-xl-offset-12{margin-left:100%}.col-xl-offset-11{margin-left:91.66666667%}.col-xl-offset-10{margin-left:83.33333333%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-8{margin-left:66.66666667%}.col-xl-offset-7{margin-left:58.33333333%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-5{margin-left:41.66666667%}.col-xl-offset-4{margin-left:33.33333333%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-2{margin-left:16.66666667%}.col-xl-offset-1{margin-left:8.33333333%}.col-xl-offset-0{margin-left:0}}@media all and (max-width:767px){.abs-clearer-mobile:after,.nav-bar:after{clear:both;content:'';display:table}.list-definition>dt{float:none}.list-definition>dd{margin-left:0}.form-row .form-label{text-align:left}.form-row .form-label.required:after{position:static}.nav{padding-bottom:0;padding-left:0;padding-right:0}.nav-bar-outer-actions{margin-top:0}.nav-bar{display:block;margin-bottom:0;margin-left:auto;margin-right:auto;width:30.9rem}.nav-bar:before{display:none}.nav-bar>li{float:left;min-height:9rem}.nav-bar>li:after{display:none}.nav-bar>li:nth-child(4n){clear:both}.nav-bar a{line-height:1.4}.tooltip{display:none!important}.readiness-check-content{margin-right:2rem}.readiness-check-side{padding:2rem 0;position:static}.form-el-insider,.form-el-insider-wrap,.page-web-configuration .form-el-insider-input,.page-web-configuration .form-el-insider-input .form-el-input{display:block;width:100%}}@media all and (max-width:479px){.nav-bar{width:23.175rem}.nav-bar>li{width:7.725rem}.nav .btn-group .btn-wrap-try-again,.nav-bar-outer-actions .btn-wrap-try-again{clear:both;display:block;float:none;margin-left:auto;margin-right:auto;margin-top:1rem;padding-top:1rem}} +.abs-action-delete,.abs-icon,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.validation-symbol:after{color:#e22626;content:'*';font-weight:400;margin-left:3px}.abs-modal-overlay,.modals-overlay{background:rgba(0,0,0,.35);bottom:0;left:0;position:fixed;right:0;top:0}.abs-action-delete>span,.abs-visually-hidden,.action-multicheck-wrap .action-multicheck-toggle>span,.admin__actions-switch-checkbox,.admin__control-fields .admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label)>.admin__field-label,.admin__field-tooltip .admin__field-tooltip-action span,.customize-your-store .customize-your-store-default .legend,.extensions-information .list .extension-delete>span,.form-el-checkbox,.form-el-radio,.selectmenu .action-delete>span,.selectmenu .action-edit>span,.selectmenu .action-save>span,.selectmenu-toggle span,.tooltip .help a span,.tooltip .help span span,[class*=admin__control-grouped]>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.abs-visually-hidden-reset,.admin__field-group-columns>.admin__field:nth-child(n+2):not(.admin__field-option):not(.admin__field-group-show-label):not(.admin__field-date)>.admin__field-label[class]{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.abs-clearfix:after,.abs-clearfix:before,.action-multicheck-wrap:after,.action-multicheck-wrap:before,.actions-split:after,.actions-split:before,.admin__control-table-pagination:after,.admin__control-table-pagination:before,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:before,.admin__data-grid-filters-footer:after,.admin__data-grid-filters-footer:before,.admin__data-grid-filters:after,.admin__data-grid-filters:before,.admin__data-grid-header-row:after,.admin__data-grid-header-row:before,.admin__field-complex:after,.admin__field-complex:before,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .magento-message .insert-title-inner:before,.modal-slide .main-col .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:before,.page-actions._fixed:after,.page-actions._fixed:before,.page-content:after,.page-content:before,.page-header-actions:after,.page-header-actions:before,.page-main-actions:not(._hidden):after,.page-main-actions:not(._hidden):before{content:'';display:table}.abs-clearfix:after,.action-multicheck-wrap:after,.actions-split:after,.admin__control-table-pagination:after,.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content:after,.admin__data-grid-filters-footer:after,.admin__data-grid-filters:after,.admin__data-grid-header-row:after,.admin__field-complex:after,.modal-slide .magento-message .insert-title-inner:after,.modal-slide .main-col .insert-title-inner:after,.page-actions._fixed:after,.page-content:after,.page-header-actions:after,.page-main-actions:not(._hidden):after{clear:both}.abs-list-reset-styles{margin:0;padding:0;list-style:none}.abs-draggable-handle,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle,.admin__control-table .draggable-handle,.data-grid .data-grid-draggable-row-cell .draggable-handle{cursor:-webkit-grab;cursor:move;font-size:0;margin-top:-4px;padding:0 1rem 0 0;vertical-align:middle;display:inline-block;text-decoration:none}.abs-draggable-handle:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:before,.admin__control-table .draggable-handle:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:before{-webkit-font-smoothing:antialiased;font-size:1.8rem;line-height:inherit;color:#9e9e9e;content:'\e617';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.abs-draggable-handle:hover:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .draggable-handle:hover:before,.admin__control-table .draggable-handle:hover:before,.data-grid .data-grid-draggable-row-cell .draggable-handle:hover:before{color:#858585}.abs-config-scope-label,.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]:before{bottom:-1.3rem;color:gray;content:attr(data-config-scope);font-size:1.1rem;font-weight:400;min-width:15rem;position:absolute;right:0;text-transform:lowercase}.abs-word-wrap,.admin__field:not(.admin__field-option)>.admin__field-label{overflow-wrap:break-word;word-wrap:break-word;-ms-word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;box-sizing:border-box}*,:after,:before{box-sizing:inherit}:focus{box-shadow:none;outline:0}._keyfocus :focus{box-shadow:0 0 0 1px #008bdb}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}embed,img,object,video{max-width:100%}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/light/opensans-300.eot);src:url(../fonts/opensans/light/opensans-300.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/light/opensans-300.woff2) format('woff2'),url(../fonts/opensans/light/opensans-300.woff) format('woff'),url(../fonts/opensans/light/opensans-300.ttf) format('truetype'),url('../fonts/opensans/light/opensans-300.svg#Open Sans') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/regular/opensans-400.eot);src:url(../fonts/opensans/regular/opensans-400.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/regular/opensans-400.woff2) format('woff2'),url(../fonts/opensans/regular/opensans-400.woff) format('woff'),url(../fonts/opensans/regular/opensans-400.ttf) format('truetype'),url('../fonts/opensans/regular/opensans-400.svg#Open Sans') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/semibold/opensans-600.eot);src:url(../fonts/opensans/semibold/opensans-600.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/semibold/opensans-600.woff2) format('woff2'),url(../fonts/opensans/semibold/opensans-600.woff) format('woff'),url(../fonts/opensans/semibold/opensans-600.ttf) format('truetype'),url('../fonts/opensans/semibold/opensans-600.svg#Open Sans') format('svg');font-weight:600;font-style:normal}@font-face{font-family:'Open Sans';src:url(../fonts/opensans/bold/opensans-700.eot);src:url(../fonts/opensans/bold/opensans-700.eot?#iefix) format('embedded-opentype'),url(../fonts/opensans/bold/opensans-700.woff2) format('woff2'),url(../fonts/opensans/bold/opensans-700.woff) format('woff'),url(../fonts/opensans/bold/opensans-700.ttf) format('truetype'),url('../fonts/opensans/bold/opensans-700.svg#Open Sans') format('svg');font-weight:700;font-style:normal}html{font-size:62.5%}body{color:#333;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.36;font-size:1.4rem}h1{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2.8rem}h2{margin:0 0 2rem;color:#41362f;font-weight:400;line-height:1.2;font-size:2rem}h3{margin:0 0 2rem;color:#41362f;font-weight:600;line-height:1.2;font-size:1.7rem}h4,h5,h6{font-weight:600;margin-top:0}p{margin:0 0 1em}small{font-size:1.2rem}a{color:#008bdb;text-decoration:none}a:hover{color:#0fa7ff;text-decoration:underline}dl,ol,ul{padding-left:0}nav ol,nav ul{list-style:none;margin:0;padding:0}html{height:100%}body{background-color:#fff;min-height:100%;min-width:102.4rem}.page-wrapper{background-color:#fff;display:inline-block;margin-left:-4px;vertical-align:top;width:calc(100% - 8.8rem)}.page-content{padding-bottom:3rem;padding-left:3rem;padding-right:3rem}.notices-wrapper{margin:0 3rem}.notices-wrapper .messages{margin-bottom:0}.row{margin-left:0;margin-right:0}.row:after{clear:both;content:'';display:table}.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9,.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{min-height:1px;padding-left:0;padding-right:0;position:relative}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}.row-gutter{margin-left:-1.5rem;margin-right:-1.5rem}.row-gutter>[class*=col-]{padding-left:1.5rem;padding-right:1.5rem}.abs-clearer:after,.extension-manager-content:after,.extension-manager-title:after,.form-row:after,.header:after,.nav:after,body:after{clear:both;content:'';display:table}.ng-cloak{display:none!important}.hide.hide{display:none}.show.show{display:block}.text-center{text-align:center}.text-right{text-align:right}@font-face{font-family:Icons;src:url(../fonts/icons/icons.eot);src:url(../fonts/icons/icons.eot?#iefix) format('embedded-opentype'),url(../fonts/icons/icons.woff2) format('woff2'),url(../fonts/icons/icons.woff) format('woff'),url(../fonts/icons/icons.ttf) format('truetype'),url(../fonts/icons/icons.svg#Icons) format('svg');font-weight:400;font-style:normal}[class*=icon-]{display:inline-block;line-height:1}.icon-failed:before,.icon-success:before,[class*=icon-]:after{font-family:Icons}.icon-success{color:#79a22e}.icon-success:before{content:'\e62d'}.icon-failed{color:#e22626}.icon-failed:before{content:'\e632'}.icon-success-thick:after{content:'\e62d'}.icon-collapse:after{content:'\e615'}.icon-failed-thick:after{content:'\e632'}.icon-expand:after{content:'\e616'}.icon-warning:after{content:'\e623'}.icon-failed-round,.icon-success-round{border-radius:100%;color:#fff;font-size:2.5rem;height:1em;position:relative;text-align:center;width:1em}.icon-failed-round:after,.icon-success-round:after{bottom:0;font-size:.5em;left:0;position:absolute;right:0;top:.45em}.icon-success-round{background-color:#79a22e}.icon-success-round:after{content:'\e62d'}.icon-failed-round{background-color:#e22626}.icon-failed-round:after{content:'\e632'}dl,ol,ul{margin-top:0}.list{padding-left:0}.list>li{display:block;margin-bottom:.75em;position:relative}.list>li>.icon-failed,.list>li>.icon-success{font-size:1.6em;left:-.1em;position:absolute;top:0}.list>li>.icon-success{color:#79a22e}.list>li>.icon-failed{color:#e22626}.list-item-failed,.list-item-icon,.list-item-success,.list-item-warning{padding-left:3.5rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{left:-.1em;position:absolute}.list-item-success:before{color:#79a22e}.list-item-failed:before{color:#e22626}.list-item-warning:before{color:#ef672f}.list-definition{margin:0 0 3rem;padding:0}.list-definition>dt{clear:left;float:left}.list-definition>dd{margin-bottom:1em;margin-left:20rem}.btn-wrap{margin:0 auto}.btn-wrap .btn{width:100%}.btn{background:#e3e3e3;border:none;color:#514943;display:inline-block;font-size:1.6rem;font-weight:600;padding:.45em .9em;text-align:center}.btn:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.btn:active{background-color:#d6d6d6}.btn.disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.ie9 .btn.disabled,.ie9 .btn[disabled]{background-color:#f0f0f0;opacity:1;text-shadow:none}.btn-large{padding:.75em 1.25em}.btn-medium{font-size:1.4rem;padding:.5em 1.5em .6em}.btn-link{background-color:transparent;border:none;color:#008bdb;font-family:1.6rem;font-size:1.5rem}.btn-link:active,.btn-link:focus,.btn-link:hover{background-color:transparent;color:#0fa7ff}.btn-prime{background-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.btn-prime:focus,.btn-prime:hover{background-color:#f65405;background-repeat:repeat-x;background-image:linear-gradient(to right,#e04f00 0,#f65405 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e04f00', endColorstr='#f65405', GradientType=1);color:#fff}.btn-prime:active{background-color:#e04f00;background-repeat:repeat-x;background-image:linear-gradient(to right,#f65405 0,#e04f00 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f65405', endColorstr='#e04f00', GradientType=1);color:#fff}.ie9 .btn-prime.disabled,.ie9 .btn-prime[disabled]{background-color:#fd6e23}.ie9 .btn-prime.disabled:active,.ie9 .btn-prime.disabled:hover,.ie9 .btn-prime[disabled]:active,.ie9 .btn-prime[disabled]:hover{background-color:#fd6e23;-webkit-filter:none;filter:none}.btn-secondary{background-color:#514943;color:#fff}.btn-secondary:hover{background-color:#5f564f;color:#fff}.btn-secondary:active,.btn-secondary:focus{background-color:#574e48;color:#fff}.ie9 .btn-secondary.disabled,.ie9 .btn-secondary[disabled]{background-color:#514943}.ie9 .btn-secondary.disabled:active,.ie9 .btn-secondary[disabled]:active{background-color:#514943;-webkit-filter:none;filter:none}[class*=btn-wrap-triangle]{overflow:hidden;position:relative}[class*=btn-wrap-triangle] .btn:after{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.btn-wrap-triangle-right{display:inline-block;padding-right:1.74rem;position:relative}.btn-wrap-triangle-right .btn{text-indent:.92rem}.btn-wrap-triangle-right .btn:after{border-color:transparent transparent transparent #e3e3e3;border-width:1.84rem 0 1.84rem 1.84rem;left:100%;margin-left:-1.74rem}.btn-wrap-triangle-right .btn:focus:after,.btn-wrap-triangle-right .btn:hover:after{border-left-color:#dbdbdb}.btn-wrap-triangle-right .btn:active:after{border-left-color:#d6d6d6}.btn-wrap-triangle-right .btn:not(.disabled):active,.btn-wrap-triangle-right .btn:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn.disabled:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:after{border-color:transparent transparent transparent #f0f0f0}.ie9 .btn-wrap-triangle-right .btn.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn.disabled:focus:after,.ie9 .btn-wrap-triangle-right .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:focus:after,.ie9 .btn-wrap-triangle-right .btn[disabled]:hover:after{border-left-color:#f0f0f0}.btn-wrap-triangle-right .btn-prime:after{border-color:transparent transparent transparent #eb5202}.btn-wrap-triangle-right .btn-prime:focus:after,.btn-wrap-triangle-right .btn-prime:hover:after{border-left-color:#f65405}.btn-wrap-triangle-right .btn-prime:active:after{border-left-color:#e04f00}.btn-wrap-triangle-right .btn-prime:not(.disabled):active,.btn-wrap-triangle-right .btn-prime:not([disabled]):active{left:1px}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:after{border-color:transparent transparent transparent #fd6e23}.ie9 .btn-wrap-triangle-right .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-right .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-right .btn-prime[disabled]:hover:after{border-left-color:#fd6e23}.btn-wrap-triangle-left{display:inline-block;padding-left:1.74rem}.btn-wrap-triangle-left .btn{text-indent:-.92rem}.btn-wrap-triangle-left .btn:after{border-color:transparent #e3e3e3 transparent transparent;border-width:1.84rem 1.84rem 1.84rem 0;margin-right:-1.74rem;right:100%}.btn-wrap-triangle-left .btn:focus:after,.btn-wrap-triangle-left .btn:hover:after{border-right-color:#dbdbdb}.btn-wrap-triangle-left .btn:active:after{border-right-color:#d6d6d6}.btn-wrap-triangle-left .btn:not(.disabled):active,.btn-wrap-triangle-left .btn:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn.disabled:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:after{border-color:transparent #f0f0f0 transparent transparent}.ie9 .btn-wrap-triangle-left .btn.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn[disabled]:hover:after{border-right-color:#f0f0f0}.btn-wrap-triangle-left .btn-prime:after{border-color:transparent #eb5202 transparent transparent}.btn-wrap-triangle-left .btn-prime:focus:after,.btn-wrap-triangle-left .btn-prime:hover:after{border-right-color:#e04f00}.btn-wrap-triangle-left .btn-prime:active:after{border-right-color:#f65405}.btn-wrap-triangle-left .btn-prime:not(.disabled):active,.btn-wrap-triangle-left .btn-prime:not([disabled]):active{right:1px}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:after{border-color:transparent #fd6e23 transparent transparent}.ie9 .btn-wrap-triangle-left .btn-prime.disabled:active:after,.ie9 .btn-wrap-triangle-left .btn-prime.disabled:hover:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:active:after,.ie9 .btn-wrap-triangle-left .btn-prime[disabled]:hover:after{border-right-color:#fd6e23}.btn-expand{background-color:transparent;border:none;color:#303030;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700;padding:0;position:relative}.btn-expand.expanded:after{border-color:transparent transparent #303030;border-width:0 .285em .36em}.btn-expand.expanded:hover:after{border-color:transparent transparent #3d3d3d}.btn-expand:hover{background-color:transparent;border:none;color:#3d3d3d}.btn-expand:hover:after{border-color:#3d3d3d transparent transparent}.btn-expand:after{border-color:#303030 transparent transparent;border-style:solid;border-width:.36em .285em 0;content:'';height:0;left:100%;margin-left:.5em;margin-top:-.18em;position:absolute;top:50%;width:0}[class*=col-] .form-el-input,[class*=col-] .form-el-select{width:100%}.form-fieldset{border:none;margin:0 0 1em;padding:0}.form-row{margin-bottom:2.2rem}.form-row .form-row{margin-bottom:.4rem}.form-row .form-label{display:block;font-weight:600;padding:.6rem 2.1em 0 0;text-align:right}.form-row .form-label.required{position:relative}.form-row .form-label.required:after{color:#eb5202;content:'*';font-size:1.15em;position:absolute;right:.7em;top:.5em}.form-row .form-el-checkbox+.form-label:before,.form-row .form-el-radio+.form-label:before{top:.7rem}.form-row .form-el-checkbox+.form-label:after,.form-row .form-el-radio+.form-label:after{top:1.1rem}.form-row.form-row-text{padding-top:.6rem}.form-row.form-row-text .action-sign-out{font-size:1.2rem;margin-left:1rem}.form-note{font-size:1.2rem;font-weight:600;margin-top:1rem}.form-el-dummy{display:none}.fieldset{border:0;margin:0;min-width:0;padding:0}input:not([disabled]):focus,textarea:not([disabled]):focus{box-shadow:none}.form-el-input{border:1px solid #adadad;color:#303030;padding:.35em .55em .5em}.form-el-input:hover{border-color:#949494}.form-el-input:focus{border-color:#008bdb}.form-el-input:required{box-shadow:none}.form-label{margin-bottom:.5em}[class*=form-label][for]{cursor:pointer}.form-el-insider-wrap{display:table;width:100%}.form-el-insider-input{display:table-cell;width:100%}.form-el-insider{border-radius:2px;display:table-cell;padding:.43em .55em .5em 0;vertical-align:top}.form-legend,.form-legend-expand,.form-legend-light{display:block;margin:0}.form-legend,.form-legend-expand{font-size:1.25em;font-weight:600;margin-bottom:2.5em;padding-top:1.5em}.form-legend{border-top:1px solid #ccc;width:100%}.form-legend-light{font-size:1em;margin-bottom:1.5em}.form-legend-expand{cursor:pointer;transition:opacity .2s linear}.form-legend-expand:hover{opacity:.85}.form-legend-expand.expanded:after{content:'\e615'}.form-legend-expand:after{content:'\e616';font-family:Icons;font-size:1.15em;font-weight:400;margin-left:.5em;vertical-align:sub}.form-el-checkbox.disabled+.form-label,.form-el-checkbox.disabled+.form-label:before,.form-el-checkbox[disabled]+.form-label,.form-el-checkbox[disabled]+.form-label:before,.form-el-radio.disabled+.form-label,.form-el-radio.disabled+.form-label:before,.form-el-radio[disabled]+.form-label,.form-el-radio[disabled]+.form-label:before{cursor:default;opacity:.5;pointer-events:none}.form-el-checkbox:not(.disabled)+.form-label:hover:before,.form-el-checkbox:not([disabled])+.form-label:hover:before,.form-el-radio:not(.disabled)+.form-label:hover:before,.form-el-radio:not([disabled])+.form-label:hover:before{border-color:#514943}.form-el-checkbox+.form-label,.form-el-radio+.form-label{font-weight:400;padding-left:2em;padding-right:0;position:relative;text-align:left;transition:border-color .1s linear}.form-el-checkbox+.form-label:before,.form-el-radio+.form-label:before{border:1px solid;content:'';left:0;position:absolute;top:.1rem;transition:border-color .1s linear}.form-el-checkbox+.form-label:before{background-color:#fff;border-color:#adadad;border-radius:2px;font-size:1.2rem;height:1.6rem;line-height:1.2;width:1.6rem}.form-el-checkbox:checked+.form-label::before{content:'\e62d';font-family:Icons}.form-el-radio+.form-label:before{background-color:#fff;border:1px solid #adadad;border-radius:100%;height:1.8rem;width:1.8rem}.form-el-radio+.form-label:after{background:0 0;border:.5rem solid transparent;border-radius:100%;content:'';height:0;left:.4rem;position:absolute;top:.5rem;transition:background .3s linear;width:0}.form-el-radio:checked+.form-label{cursor:default}.form-el-radio:checked+.form-label:after{border-color:#514943}.form-select-label{border:1px solid #adadad;color:#303030;cursor:pointer;display:block;overflow:hidden;position:relative;z-index:0}.form-select-label:hover,.form-select-label:hover:after{border-color:#949494}.form-select-label:active,.form-select-label:active:after,.form-select-label:focus,.form-select-label:focus:after{border-color:#008bdb}.form-select-label:after{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:2.36em;z-index:-2}.ie9 .form-select-label:after{display:none}.form-select-label:before{border-color:#303030 transparent transparent;border-style:solid;border-width:5px 4px 0;content:'';height:0;margin-right:-4px;margin-top:-2.5px;position:absolute;right:1.18em;top:50%;width:0;z-index:-1}.ie9 .form-select-label:before{display:none}.form-select-label .form-el-select{background:0 0;border:none;border-radius:0;content:'';display:block;margin:0;padding:.35em calc(2.36em + 10%) .5em .55em;width:110%}.ie9 .form-select-label .form-el-select{padding-right:.55em;width:100%}.form-select-label .form-el-select::-ms-expand{display:none}.form-el-select{background:#fff;border:1px solid #adadad;border-radius:2px;color:#303030;display:block;padding:.35em .55em}.multiselect-custom{border:1px solid #adadad;height:45.2rem;margin:0 0 1.5rem;overflow:auto;position:relative}.multiselect-custom ul{margin:0;padding:0;list-style:none;min-width:29rem}.multiselect-custom .item{padding:1rem 1.4rem}.multiselect-custom .selected{background-color:#e0f6fe}.multiselect-custom .form-label{margin-bottom:0}[class*=form-el-].invalid{border-color:#e22626}[class*=form-el-].invalid+.error-container{display:block}.error-container{background-color:#fffbbb;border:1px solid #ee7d7d;color:#514943;display:none;font-size:1.19rem;margin-top:.2rem;padding:.8rem 1rem .9rem}.check-result-message{margin-left:.5em;min-height:3.68rem;-ms-align-items:center;-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex}.check-result-text{margin-left:.5em}body:not([class]){min-width:0}.container{display:block;margin:0 auto 4rem;max-width:100rem;padding:0}.abs-action-delete,.action-close:before,.action-next:before,.action-previous:before,.admin-user .admin__action-dropdown:before,.admin__action-multiselect-dropdown:before,.admin__action-multiselect-search-label:before,.admin__control-checkbox+label:before,.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before,.admin__control-table .action-delete:before,.admin__current-filters-list .action-remove:before,.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before,.admin__data-grid-action-bookmarks .admin__action-dropdown:before,.admin__data-grid-action-columns .admin__action-dropdown:before,.admin__data-grid-action-export .admin__action-dropdown:before,.admin__field-fallback-reset:before,.admin__menu .level-0>a:before,.admin__page-nav-item-message .admin__page-nav-item-message-icon,.admin__page-nav-title._collapsible:after,.data-grid-filters-action-wrap .action-default:before,.data-grid-row-changed:after,.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before,.data-grid-search-control-wrap .action-submit:before,.extensions-information .list .extension-delete,.icon-failed:before,.icon-success:before,.notifications-action:before,.notifications-close:before,.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before,.page-title-jumbo-success:before,.search-global-label:before,.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before,.setup-home-item:before,.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before,.store-switcher .dropdown-menu .dropdown-toolbar a:before,.tooltip .help a:before,.tooltip .help span:before{-webkit-font-smoothing:antialiased;font-family:Icons;font-style:normal;font-weight:400;line-height:1;speak:none}.text-stretch{margin-bottom:1.5em}.page-title-jumbo{font-size:4rem;font-weight:300;letter-spacing:-.05em;margin-bottom:2.9rem}.page-title-jumbo-success:before{color:#79a22e;content:'\e62d';font-size:3.9rem;margin-left:-.3rem;margin-right:2.4rem}.list{margin-bottom:3rem}.list-dot .list-item{display:list-item;list-style-position:inside;margin-bottom:1.2rem}.list-title{color:#333;font-size:1.4rem;font-weight:700;letter-spacing:.025em;margin-bottom:1.2rem}.list-item-failed:before,.list-item-success:before,.list-item-warning:before{font-family:Icons;font-size:1.6rem;top:0}.list-item-success:before{content:'\e62d';font-size:1.6rem}.list-item-failed:before{content:'\e632';font-size:1.4rem;left:.1rem;top:.2rem}.list-item-warning:before{content:'\e623';font-size:1.3rem;left:.2rem}.form-wrap{margin-bottom:3.6rem;padding-top:2.1rem}.form-el-label-horizontal{display:inline-block;font-size:1.3rem;font-weight:600;letter-spacing:.025em;margin-bottom:.4rem;margin-left:.4rem}.app-updater{min-width:768px}body._has-modal{height:100%;overflow:hidden;width:100%}.modals-overlay{z-index:899}.modal-popup,.modal-slide{bottom:0;min-width:0;position:fixed;right:0;top:0;visibility:hidden}.modal-popup._show,.modal-slide._show{visibility:visible}.modal-popup._show .modal-inner-wrap,.modal-slide._show .modal-inner-wrap{-ms-transform:translate(0,0);transform:translate(0,0)}.modal-popup .modal-inner-wrap,.modal-slide .modal-inner-wrap{background-color:#fff;box-shadow:0 0 12px 2px rgba(0,0,0,.35);opacity:1;pointer-events:auto}.modal-slide{left:14.8rem;z-index:900}.modal-slide._show .modal-inner-wrap{-ms-transform:translateX(0);transform:translateX(0)}.modal-slide .modal-inner-wrap{height:100%;overflow-y:auto;position:static;-ms-transform:translateX(100%);transform:translateX(100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;width:auto}.modal-slide._inner-scroll .modal-inner-wrap{overflow-y:visible;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.modal-slide._inner-scroll .modal-footer,.modal-slide._inner-scroll .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-slide._inner-scroll .modal-content{overflow-y:auto}.modal-slide._inner-scroll .modal-footer{margin-top:auto}.modal-slide .modal-content,.modal-slide .modal-footer,.modal-slide .modal-header{padding:0 2.6rem 2.6rem}.modal-slide .modal-header{padding-bottom:2.1rem;padding-top:2.1rem}.modal-popup{z-index:900;left:0;overflow-y:auto}.modal-popup._show .modal-inner-wrap{-ms-transform:translateY(0);transform:translateY(0)}.modal-popup .modal-inner-wrap{margin:5rem auto;width:75%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;box-sizing:border-box;height:auto;left:0;position:absolute;right:0;-ms-transform:translateY(-200%);transform:translateY(-200%);transition-duration:.2s;transition-property:transform,visibility;transition-timing-function:ease}.modal-popup._inner-scroll{overflow-y:visible}.ie10 .modal-popup._inner-scroll,.ie9 .modal-popup._inner-scroll{overflow-y:auto}.modal-popup._inner-scroll .modal-inner-wrap{max-height:90%}.ie10 .modal-popup._inner-scroll .modal-inner-wrap,.ie9 .modal-popup._inner-scroll .modal-inner-wrap{max-height:none}.modal-popup._inner-scroll .modal-content{overflow-y:auto}.modal-popup .modal-content,.modal-popup .modal-footer,.modal-popup .modal-header{padding-left:3rem;padding-right:3rem}.modal-popup .modal-footer,.modal-popup .modal-header{-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0}.modal-popup .modal-header{padding-bottom:1.2rem;padding-top:3rem}.modal-popup .modal-footer{margin-top:auto;padding-bottom:3rem}.modal-popup .modal-footer-actions{text-align:right}.admin__action-dropdown-wrap{display:inline-block;position:relative}.admin__action-dropdown-wrap .admin__action-dropdown-text:after{left:-6px;right:0}.admin__action-dropdown-wrap .admin__action-dropdown-menu{left:auto;right:0}.admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__action-dropdown-wrap.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin__action-dropdown-wrap._active .admin__action-dropdown-text:after,.admin__action-dropdown-wrap.active .admin__action-dropdown-text:after{background-color:#fff;content:'';height:6px;position:absolute;top:100%}.admin__action-dropdown-wrap._active .admin__action-dropdown-menu,.admin__action-dropdown-wrap.active .admin__action-dropdown-menu{display:block}.admin__action-dropdown-wrap._disabled .admin__action-dropdown{cursor:default}.admin__action-dropdown-wrap._disabled:hover .admin__action-dropdown{color:#333}.admin__action-dropdown{background-color:#fff;border:1px solid transparent;border-bottom:none;border-radius:0;box-shadow:none;color:#333;display:inline-block;font-size:1.3rem;font-weight:400;letter-spacing:-.025em;padding:.7rem 3.3rem .8rem 1.5rem;position:relative;vertical-align:baseline;z-index:2}.admin__action-dropdown._active:after,.admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .admin__action-dropdown:after,.active .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin__action-dropdown:focus,.admin__action-dropdown:hover{background-color:#fff;color:#000;text-decoration:none}.admin__action-dropdown:after{right:1.5rem}.admin__action-dropdown:before{margin-right:1rem}.admin__action-dropdown-menu{background-color:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;line-height:1.36;margin-top:-1px;min-width:120%;padding:.5rem 1rem;position:absolute;top:100%;transition:all .15s ease;z-index:1}.admin__action-dropdown-menu>li{display:block}.admin__action-dropdown-menu>li>a{color:#333;display:block;text-decoration:none;padding:.6rem .5rem}.selectmenu{display:inline-block;position:relative;text-align:left;z-index:1}.selectmenu._active{border-color:#007bdb;z-index:500}.selectmenu .action-delete,.selectmenu .action-edit,.selectmenu .action-save{background-color:transparent;border-color:transparent;box-shadow:none;padding:0 1rem}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover,.selectmenu .action-save:hover{background-color:transparent;border-color:transparent;box-shadow:none}.selectmenu .action-delete:before,.selectmenu .action-edit:before,.selectmenu .action-save:before{content:'\e630'}.selectmenu .action-delete,.selectmenu .action-edit{border:0 solid #fff;border-left-width:1px;bottom:0;position:absolute;right:0;top:0;z-index:1}.selectmenu .action-delete:hover,.selectmenu .action-edit:hover{border:0 solid #fff;border-left-width:1px}.selectmenu .action-save:before{content:'\e625'}.selectmenu .action-edit:before{content:'\e631'}.selectmenu-value{display:inline-block}.selectmenu-value input[type=text]{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:0;display:inline;margin:0;width:6rem}body._keyfocus .selectmenu-value input[type=text]:focus{box-shadow:none}.selectmenu-toggle{padding-right:3rem;background:0 0;border-width:0;bottom:0;float:right;position:absolute;right:0;top:0;width:0}.selectmenu-toggle._active:after,.selectmenu-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.1rem;top:50%;transition:all .2s linear;width:0}._active .selectmenu-toggle:after,.active .selectmenu-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.selectmenu-toggle:hover:after{border-color:#000 transparent transparent}.selectmenu-toggle:active,.selectmenu-toggle:focus,.selectmenu-toggle:hover{background:0 0}.selectmenu._active .selectmenu-toggle:before{border-color:#007bdb}body._keyfocus .selectmenu-toggle:focus{box-shadow:none}.selectmenu-toggle:before{background:#e3e3e3;border-left:1px solid #adadad;bottom:0;content:'';display:block;position:absolute;right:0;top:0;width:3.2rem}.selectmenu-items{background:#fff;border:1px solid #007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);display:none;float:left;left:-1px;margin-top:3px;max-width:20rem;min-width:calc(100% + 2px);position:absolute;top:100%}.selectmenu-items._active{display:block}.selectmenu-items ul{float:left;list-style-type:none;margin:0;min-width:100%;padding:0}.selectmenu-items li{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row;transition:background .2s linear}.selectmenu-items li:hover{background:#e3e3e3}.selectmenu-items li:last-child .selectmenu-item-action,.selectmenu-items li:last-child .selectmenu-item-action:visited{color:#008bdb;text-decoration:none}.selectmenu-items li:last-child .selectmenu-item-action:hover{color:#0fa7ff;text-decoration:underline}.selectmenu-items li:last-child .selectmenu-item-action:active{color:#ff5501;text-decoration:underline}.selectmenu-item{position:relative;width:100%;z-index:1}li._edit>.selectmenu-item{display:none}.selectmenu-item-edit{display:none;padding:.3rem 4rem .3rem .4rem;position:relative;white-space:nowrap;z-index:1}li:last-child .selectmenu-item-edit{padding-right:.4rem}.selectmenu-item-edit .admin__control-text{margin:0;width:5.4rem}li._edit .selectmenu-item-edit{display:block}.selectmenu-item-action{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background:0 0;border:0;color:#333;display:block;font-size:1.4rem;font-weight:400;min-width:100%;padding:1rem 6rem 1rem 1.5rem;text-align:left;transition:background .2s linear;width:5rem}.selectmenu-item-action:focus,.selectmenu-item-action:hover{background:#e3e3e3}.abs-actions-split-xl .action-default,.page-actions .actions-split .action-default{margin-right:4rem}.abs-actions-split-xl .action-toggle,.page-actions .actions-split .action-toggle{padding-right:4rem}.abs-actions-split-xl .action-toggle:after,.page-actions .actions-split .action-toggle:after{border-width:.9rem .6rem 0;margin-top:-.3rem;right:1.4rem}.actions-split{position:relative;z-index:400}.actions-split._active,.actions-split.active,.actions-split:hover{box-shadow:0 0 0 1px #007bdb}.actions-split._active .action-toggle.action-primary,.actions-split._active .action-toggle.primary,.actions-split.active .action-toggle.action-primary,.actions-split.active .action-toggle.primary{background-color:#ba4000;border-color:#ba4000}.actions-split._active .dropdown-menu,.actions-split.active .dropdown-menu{opacity:1;visibility:visible;display:block}.actions-split .action-default,.actions-split .action-toggle{float:left;margin:0}.actions-split .action-default._active,.actions-split .action-default.active,.actions-split .action-default:hover,.actions-split .action-toggle._active,.actions-split .action-toggle.active,.actions-split .action-toggle:hover{box-shadow:none}.actions-split .action-default{margin-right:3.2rem;min-width:9.3rem}.actions-split .action-toggle{padding-right:3.2rem;border-left-color:rgba(0,0,0,.2);bottom:0;padding-left:0;position:absolute;right:0;top:0}.actions-split .action-toggle._active:after,.actions-split .action-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .actions-split .action-toggle:after,.active .actions-split .action-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.actions-split .action-toggle:hover:after{border-color:#000 transparent transparent}.actions-split .action-toggle.action-primary:after,.actions-split .action-toggle.action-secondary:after,.actions-split .action-toggle.primary:after,.actions-split .action-toggle.secondary:after{border-color:#fff transparent transparent}.actions-split .action-toggle>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-select-wrap{display:inline-block;position:relative}.action-select-wrap .action-select{padding-right:3.2rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background-color:#fff;font-weight:400;text-align:left}.action-select-wrap .action-select._active:after,.action-select-wrap .action-select.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.2rem;top:50%;transition:all .2s linear;width:0}._active .action-select-wrap .action-select:after,.active .action-select-wrap .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .action-select:hover:after{border-color:#000 transparent transparent}.action-select-wrap .action-select:hover,.action-select-wrap .action-select:hover:before{border-color:#878787}.action-select-wrap .action-select:before{background-color:#e3e3e3;border:1px solid #adadad;bottom:0;content:'';position:absolute;right:0;top:0;width:3.2rem}.action-select-wrap .action-select._active{border-color:#007bdb}.action-select-wrap .action-select._active:before{border-color:#007bdb #007bdb #007bdb #adadad}.action-select-wrap .action-select[disabled]{color:#333}.action-select-wrap .action-select[disabled]:after{border-color:#333 transparent transparent}.action-select-wrap._active{z-index:500}.action-select-wrap._active .action-select,.action-select-wrap._active .action-select:before{border-color:#007bdb}.action-select-wrap._active .action-select:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-select-wrap .abs-action-menu .action-submenu,.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu,.action-select-wrap .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:45rem;overflow-y:auto}.action-select-wrap .abs-action-menu .action-submenu ._disabled:hover,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .action-menu ._disabled:hover,.action-select-wrap .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled:hover,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled:hover{background:#fff}.action-select-wrap .abs-action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .abs-action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .action-menu ._disabled .action-menu-item,.action-select-wrap .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu ._disabled .action-menu-item,.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu ._disabled .action-menu-item{cursor:default;opacity:.5}.action-select-wrap .action-menu-items{left:0;position:absolute;right:0;top:100%}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu{min-width:100%;position:static}.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.abs-action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu,.action-select-wrap .action-menu-items>.action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .action-menu .action-submenu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu,.action-select-wrap .action-menu-items>.actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{position:absolute}.action-multicheck-wrap{display:inline-block;height:1.6rem;padding-top:1px;position:relative;width:3.1rem;z-index:200}.action-multicheck-wrap:hover .action-multicheck-toggle,.action-multicheck-wrap:hover .admin__control-checkbox+label:before{border-color:#878787}.action-multicheck-wrap._active .action-multicheck-toggle,.action-multicheck-wrap._active .admin__control-checkbox+label:before{border-color:#007bdb}.action-multicheck-wrap._active .abs-action-menu .action-submenu,.action-multicheck-wrap._active .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .action-menu,.action-multicheck-wrap._active .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu,.action-multicheck-wrap._active .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap._active .actions-split .dropdown-menu .action-submenu .action-submenu{opacity:1;visibility:visible;display:block}.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{background-color:#fff}.action-multicheck-wrap._disabled .action-multicheck-toggle,.action-multicheck-wrap._disabled .admin__control-checkbox+label:before{border-color:#adadad;opacity:1}.action-multicheck-wrap .action-multicheck-toggle,.action-multicheck-wrap .admin__control-checkbox,.action-multicheck-wrap .admin__control-checkbox+label{float:left}.action-multicheck-wrap .action-multicheck-toggle{border-radius:0 1px 1px 0;height:1.6rem;margin-left:-1px;padding:0;position:relative;transition:border-color .1s linear;width:1.6rem}.action-multicheck-wrap .action-multicheck-toggle._active:after,.action-multicheck-wrap .action-multicheck-toggle.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:after{border-color:#000 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;top:50%;transition:all .2s linear;width:0}._active .action-multicheck-wrap .action-multicheck-toggle:after,.active .action-multicheck-wrap .action-multicheck-toggle:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.action-multicheck-wrap .action-multicheck-toggle:hover:after{border-color:#000 transparent transparent}.action-multicheck-wrap .action-multicheck-toggle:focus{border-color:#007bdb}.action-multicheck-wrap .action-multicheck-toggle:after{right:.3rem}.action-multicheck-wrap .abs-action-menu .action-submenu,.action-multicheck-wrap .abs-action-menu .action-submenu .action-submenu,.action-multicheck-wrap .action-menu,.action-multicheck-wrap .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu,.action-multicheck-wrap .actions-split .action-menu .action-submenu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu,.action-multicheck-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:-1.1rem;margin-top:1px;right:auto;text-align:left}.action-multicheck-wrap .action-menu-item{white-space:nowrap}.admin__action-multiselect-wrap{display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.admin__action-multiselect-wrap.action-select-wrap:focus{box-shadow:none}.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .action-menu,.admin__action-multiselect-wrap.action-select-wrap .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-wrap.action-select-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:none;overflow-y:inherit}.admin__action-multiselect-wrap .action-menu-item{transition:background-color .1s linear}.admin__action-multiselect-wrap .action-menu-item._selected{background-color:#e0f6fe}.admin__action-multiselect-wrap .action-menu-item._hover{background-color:#e3e3e3}.admin__action-multiselect-wrap .action-menu-item._unclickable{cursor:default}.admin__action-multiselect-wrap .admin__action-multiselect{border:1px solid #adadad;cursor:pointer;display:block;min-height:3.2rem;padding-right:3.6rem;white-space:normal}.admin__action-multiselect-wrap .admin__action-multiselect:after{bottom:1.25rem;top:auto}.admin__action-multiselect-wrap .admin__action-multiselect:before{height:3.3rem;top:auto}.admin__control-table-wrapper .admin__action-multiselect-wrap{position:static}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect{position:relative}.admin__control-table-wrapper .admin__action-multiselect-wrap .admin__action-multiselect:before{right:-1px;top:-1px}.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .abs-action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu,.admin__control-table-wrapper .admin__action-multiselect-wrap .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .action-menu .action-submenu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu,.admin__control-table-wrapper .admin__action-multiselect-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:34rem;right:auto;top:auto;z-index:1}.admin__action-multiselect-wrap .admin__action-multiselect-item-path{color:#a79d95;font-size:1.2rem;font-weight:400;padding-left:1rem}.admin__action-multiselect-actions-wrap{border-top:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;text-align:center}.admin__action-multiselect-actions-wrap .action-default{font-size:1.3rem;min-width:13rem}.admin__action-multiselect-text{padding:.6rem 1rem}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{text-align:left}.admin__action-multiselect-label{cursor:pointer;position:relative;z-index:1}.admin__action-multiselect-label:before{margin-right:.5rem}._unclickable .admin__action-multiselect-label{cursor:default;font-weight:700}.admin__action-multiselect-search-wrap{border-bottom:1px solid #e3e3e3;margin:0 1rem;padding:1rem 0;position:relative}.admin__action-multiselect-search{padding-right:3rem;width:100%}.admin__action-multiselect-search-label{display:block;font-size:1.5rem;height:1em;overflow:hidden;position:absolute;right:2.2rem;top:1.7rem;width:1em}.admin__action-multiselect-search-label:before{content:'\e60c'}.admin__action-multiselect-search-count{color:#a79d95;margin-top:1rem}.admin__action-multiselect-menu-inner{margin-bottom:0;max-height:46rem;overflow-y:auto}.admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{list-style:none;max-height:none;overflow:hidden;padding-left:2.2rem}.admin__action-multiselect-menu-inner ._hidden{display:none}.admin__action-multiselect-crumb{background-color:#f5f5f5;border:1px solid #a79d95;border-radius:1px;display:inline-block;font-size:1.2rem;margin:.3rem -4px .3rem .3rem;padding:.3rem 2.4rem .4rem 1rem;position:relative;transition:border-color .1s linear}.admin__action-multiselect-crumb:hover{border-color:#908379}.admin__action-multiselect-crumb .action-close{bottom:0;font-size:.5em;position:absolute;right:0;top:0;width:2rem}.admin__action-multiselect-crumb .action-close:hover{color:#000}.admin__action-multiselect-crumb .action-close:active,.admin__action-multiselect-crumb .action-close:focus{background-color:transparent}.admin__action-multiselect-crumb .action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__action-multiselect-tree .abs-action-menu .action-submenu,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .action-menu,.admin__action-multiselect-tree .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu{min-width:34.7rem}.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .abs-action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-menu-item,.admin__action-multiselect-tree .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-menu-item,.admin__action-multiselect-tree .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item{margin-top:.1rem}.admin__action-multiselect-tree .action-menu-item{margin-left:4.2rem;position:relative}.admin__action-multiselect-tree .action-menu-item._expended:before{border-left:1px dashed #a79d95;bottom:0;content:'';left:-1rem;position:absolute;top:1rem;width:1px}.admin__action-multiselect-tree .action-menu-item._expended .admin__action-multiselect-dropdown:before{content:'\e615'}.admin__action-multiselect-tree .action-menu-item._with-checkbox .admin__action-multiselect-label{padding-left:2.6rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner{padding-left:3.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner .admin__action-multiselect-menu-inner:before{left:4.3rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item{position:relative}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:last-child:before{height:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after,.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{content:'';left:0;position:absolute}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:after{border-top:1px dashed #a79d95;height:1px;top:2.1rem;width:5.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item:before{border-left:1px dashed #a79d95;height:100%;top:0;width:1px}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._parent:after{width:4.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root{margin-left:-1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:after{left:3.2rem;width:2.2rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:before{left:3.2rem;top:1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root._parent:after{display:none}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:first-child:before{top:2.1rem}.admin__action-multiselect-tree .admin__action-multiselect-menu-inner-item._root:last-child:before{height:1rem}.admin__action-multiselect-tree .admin__action-multiselect-label{line-height:2.2rem;vertical-align:middle;word-break:break-all}.admin__action-multiselect-tree .admin__action-multiselect-label:before{left:0;position:absolute;top:.4rem}.admin__action-multiselect-dropdown{border-radius:50%;height:2.2rem;left:-2.2rem;position:absolute;top:1rem;width:2.2rem;z-index:1}.admin__action-multiselect-dropdown:before{background:#fff;color:#a79d95;content:'\e616';font-size:2.2rem}.admin__actions-switch{display:inline-block;position:relative;vertical-align:middle}.admin__field-control .admin__actions-switch{line-height:3.2rem}.admin__actions-switch+.admin__field-service{min-width:34rem}._disabled .admin__actions-switch-checkbox+.admin__actions-switch-label,.admin__actions-switch-checkbox.disabled+.admin__actions-switch-label{cursor:not-allowed;opacity:.5;pointer-events:none}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:before{left:15px}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label:after{background:#79a22e}.admin__actions-switch-checkbox:checked+.admin__actions-switch-label .admin__actions-switch-text:before{content:attr(data-text-on)}.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:after,.admin__actions-switch-checkbox:focus+.admin__actions-switch-label:before{border-color:#007bdb}._error .admin__actions-switch-checkbox+.admin__actions-switch-label:after,._error .admin__actions-switch-checkbox+.admin__actions-switch-label:before{border-color:#e22626}.admin__actions-switch-label{cursor:pointer;display:inline-block;height:22px;line-height:22px;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle}.admin__actions-switch-label:after,.admin__actions-switch-label:before{left:0;position:absolute;right:auto;top:0}.admin__actions-switch-label:before{background:#fff;border:1px solid #aaa6a0;border-radius:100%;content:'';display:block;height:22px;transition:left .2s ease-in 0s;width:22px;z-index:1}.admin__actions-switch-label:after{background:#e3e3e3;border:1px solid #aaa6a0;border-radius:12px;content:'';display:block;height:22px;transition:background .2s ease-in 0s;vertical-align:middle;width:37px;z-index:0}.admin__actions-switch-text:before{content:attr(data-text-off);padding-left:47px;white-space:nowrap}.abs-action-delete,.abs-action-reset,.action-close,.admin__field-fallback-reset,.extensions-information .list .extension-delete,.notifications-close,.search-global-field._active .search-global-action{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0}.abs-action-delete:hover,.abs-action-reset:hover,.action-close:hover,.admin__field-fallback-reset:hover,.extensions-information .list .extension-delete:hover,.notifications-close:hover,.search-global-field._active .search-global-action:hover{background-color:transparent;border:none;box-shadow:none}.abs-action-default,.abs-action-pattern,.abs-action-primary,.abs-action-quaternary,.abs-action-secondary,.abs-action-tertiary,.action-default,.action-primary,.action-quaternary,.action-secondary,.action-tertiary,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions>button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary,button,button.primary,button.secondary,button.tertiary{border:1px solid;border-radius:0;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:1.36;padding:.6rem 1em;text-align:center;vertical-align:baseline}.abs-action-default.disabled,.abs-action-default[disabled],.abs-action-pattern.disabled,.abs-action-pattern[disabled],.abs-action-primary.disabled,.abs-action-primary[disabled],.abs-action-quaternary.disabled,.abs-action-quaternary[disabled],.abs-action-secondary.disabled,.abs-action-secondary[disabled],.abs-action-tertiary.disabled,.abs-action-tertiary[disabled],.action-default.disabled,.action-default[disabled],.action-primary.disabled,.action-primary[disabled],.action-quaternary.disabled,.action-quaternary[disabled],.action-secondary.disabled,.action-secondary[disabled],.action-tertiary.disabled,.action-tertiary[disabled],.modal-popup .modal-footer .action-primary.disabled,.modal-popup .modal-footer .action-primary[disabled],.modal-popup .modal-footer .action-secondary.disabled,.modal-popup .modal-footer .action-secondary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.action-secondary.disabled,.page-actions .page-actions-buttons>button.action-secondary[disabled],.page-actions .page-actions-buttons>button.disabled,.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions .page-actions-buttons>button[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.action-secondary.disabled,.page-actions>button.action-secondary[disabled],.page-actions>button.disabled,.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],.page-actions>button[disabled],button.disabled,button.primary.disabled,button.primary[disabled],button.secondary.disabled,button.secondary[disabled],button.tertiary.disabled,button.tertiary[disabled],button[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-l,.modal-popup .modal-footer .action-primary,.modal-popup .modal-footer .action-secondary,.page-actions .page-actions-buttons>button,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions .page-actions-buttons>button.primary,.page-actions button,.page-actions>button.action-primary,.page-actions>button.action-secondary,.page-actions>button.primary{font-size:1.6rem;letter-spacing:.025em;padding-bottom:.6875em;padding-top:.6875em}.abs-action-delete,.extensions-information .list .extension-delete{display:inline-block;font-size:1.6rem;margin-left:1.2rem;padding-top:.7rem;text-decoration:none;vertical-align:middle}.abs-action-delete:after,.extensions-information .list .extension-delete:after{color:#666;content:'\e630'}.abs-action-delete:hover:after,.extensions-information .list .extension-delete:hover:after{color:#35302c}.abs-action-button-as-link,.action-advanced,.data-grid .action-delete{line-height:1.36;padding:0;color:#008bdb;text-decoration:none;background:0 0;border:0;display:inline;font-weight:400;border-radius:0}.abs-action-button-as-link:visited,.action-advanced:visited,.data-grid .action-delete:visited{color:#008bdb;text-decoration:none}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{text-decoration:underline}.abs-action-button-as-link:active,.action-advanced:active,.data-grid .action-delete:active{color:#ff5501;text-decoration:underline}.abs-action-button-as-link:hover,.action-advanced:hover,.data-grid .action-delete:hover{color:#0fa7ff}.abs-action-button-as-link:active,.abs-action-button-as-link:focus,.abs-action-button-as-link:hover,.action-advanced:active,.action-advanced:focus,.action-advanced:hover,.data-grid .action-delete:active,.data-grid .action-delete:focus,.data-grid .action-delete:hover{background:0 0;border:0}.abs-action-button-as-link.disabled,.abs-action-button-as-link[disabled],.action-advanced.disabled,.action-advanced[disabled],.data-grid .action-delete.disabled,.data-grid .action-delete[disabled],fieldset[disabled] .abs-action-button-as-link,fieldset[disabled] .action-advanced,fieldset[disabled] .data-grid .action-delete{color:#008bdb;opacity:.5;cursor:default;pointer-events:none;text-decoration:underline}.abs-action-button-as-link:active,.abs-action-button-as-link:not(:focus),.action-advanced:active,.action-advanced:not(:focus),.data-grid .action-delete:active,.data-grid .action-delete:not(:focus){box-shadow:none}.abs-action-button-as-link:focus,.action-advanced:focus,.data-grid .action-delete:focus{color:#0fa7ff}.abs-action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.abs-action-default:active,.abs-action-default:focus,.abs-action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.abs-action-primary,.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary,button.primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.abs-action-primary:active,.abs-action-primary:focus,.abs-action-primary:hover,.page-actions .page-actions-buttons>button.action-primary:active,.page-actions .page-actions-buttons>button.action-primary:focus,.page-actions .page-actions-buttons>button.action-primary:hover,.page-actions .page-actions-buttons>button.primary:active,.page-actions .page-actions-buttons>button.primary:focus,.page-actions .page-actions-buttons>button.primary:hover,.page-actions>button.action-primary:active,.page-actions>button.action-primary:focus,.page-actions>button.action-primary:hover,.page-actions>button.primary:active,.page-actions>button.primary:focus,.page-actions>button.primary:hover,button.primary:active,button.primary:focus,button.primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-primary.disabled,.abs-action-primary[disabled],.page-actions .page-actions-buttons>button.action-primary.disabled,.page-actions .page-actions-buttons>button.action-primary[disabled],.page-actions .page-actions-buttons>button.primary.disabled,.page-actions .page-actions-buttons>button.primary[disabled],.page-actions>button.action-primary.disabled,.page-actions>button.action-primary[disabled],.page-actions>button.primary.disabled,.page-actions>button.primary[disabled],button.primary.disabled,button.primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.abs-action-secondary,.modal-popup .modal-footer .action-primary,.page-actions .page-actions-buttons>button.action-secondary,.page-actions>button.action-secondary,button.secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.abs-action-secondary:active,.abs-action-secondary:focus,.abs-action-secondary:hover,.modal-popup .modal-footer .action-primary:active,.modal-popup .modal-footer .action-primary:focus,.modal-popup .modal-footer .action-primary:hover,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions .page-actions-buttons>button.action-secondary:focus,.page-actions .page-actions-buttons>button.action-secondary:hover,.page-actions>button.action-secondary:active,.page-actions>button.action-secondary:focus,.page-actions>button.action-secondary:hover,button.secondary:active,button.secondary:focus,button.secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.abs-action-secondary:active,.modal-popup .modal-footer .action-primary:active,.page-actions .page-actions-buttons>button.action-secondary:active,.page-actions>button.action-secondary:active,button.secondary:active{background-color:#35302c}.abs-action-tertiary,.modal-popup .modal-footer .action-secondary,button.tertiary{background-color:transparent;border-color:transparent;text-shadow:none;color:#008bdb}.abs-action-tertiary:active,.abs-action-tertiary:focus,.abs-action-tertiary:hover,.modal-popup .modal-footer .action-secondary:active,.modal-popup .modal-footer .action-secondary:focus,.modal-popup .modal-footer .action-secondary:hover,button.tertiary:active,button.tertiary:focus,button.tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#0fa7ff;text-decoration:underline}.abs-action-quaternary,.page-actions .page-actions-buttons>button,.page-actions>button{background-color:transparent;border-color:transparent;text-shadow:none;color:#333}.abs-action-quaternary:active,.abs-action-quaternary:focus,.abs-action-quaternary:hover,.page-actions .page-actions-buttons>button:active,.page-actions .page-actions-buttons>button:focus,.page-actions .page-actions-buttons>button:hover,.page-actions>button:active,.page-actions>button:focus,.page-actions>button:hover{background-color:transparent;border-color:transparent;box-shadow:none;color:#1a1a1a}.abs-action-menu,.actions-split .abs-action-menu .action-submenu,.actions-split .abs-action-menu .action-submenu .action-submenu,.actions-split .action-menu,.actions-split .action-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.actions-split .dropdown-menu{text-align:left;background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu._active,.actions-split .abs-action-menu .action-submenu .action-submenu._active,.actions-split .abs-action-menu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .action-menu._active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .actions-split .dropdown-menu .action-submenu._active,.actions-split .dropdown-menu._active{display:block}.abs-action-menu>li,.actions-split .abs-action-menu .action-submenu .action-submenu>li,.actions-split .abs-action-menu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .action-menu>li,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .actions-split .dropdown-menu .action-submenu>li,.actions-split .dropdown-menu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu>li>a:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .abs-action-menu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .action-menu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu>li>a:hover{text-decoration:none}.abs-action-menu>li._visible,.abs-action-menu>li:hover,.actions-split .abs-action-menu .action-submenu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu .action-submenu>li:hover,.actions-split .abs-action-menu .action-submenu>li._visible,.actions-split .abs-action-menu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .action-menu>li._visible,.actions-split .action-menu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .actions-split .dropdown-menu .action-submenu>li:hover,.actions-split .dropdown-menu>li._visible,.actions-split .dropdown-menu>li:hover{background-color:#e3e3e3}.abs-action-menu>li:active,.actions-split .abs-action-menu .action-submenu .action-submenu>li:active,.actions-split .abs-action-menu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .action-menu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .actions-split .dropdown-menu .action-submenu>li:active,.actions-split .dropdown-menu>li:active{background-color:#cacaca}.abs-action-menu>li._parent,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent,.actions-split .abs-action-menu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .action-menu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent,.actions-split .dropdown-menu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .abs-action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-menu-item,.abs-action-menu .item,.actions-split .abs-action-menu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu .item,.actions-split .abs-action-menu .action-submenu .item,.actions-split .action-menu .action-menu-item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .item,.actions-split .action-menu .item,.actions-split .actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .actions-split .dropdown-menu .action-submenu .item,.actions-split .dropdown-menu .action-menu-item,.actions-split .dropdown-menu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu,.ie9 .actions-split .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu a.action-menu-item,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .abs-action-menu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .action-menu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu a.action-menu-item{color:#333}.abs-action-menu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .abs-action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .action-menu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .actions-split .dropdown-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.abs-action-wrap-triangle{position:relative}.abs-action-wrap-triangle .action-default{width:100%}.abs-action-wrap-triangle .action-default:after,.abs-action-wrap-triangle .action-default:before{border-style:solid;content:'';height:0;position:absolute;top:0;width:0}.abs-action-wrap-triangle .action-default:active,.abs-action-wrap-triangle .action-default:focus,.abs-action-wrap-triangle .action-default:hover{box-shadow:none}._keyfocus .abs-action-wrap-triangle .action-default:focus{box-shadow:0 0 0 1px #007bdb}.ie10 .abs-action-wrap-triangle .action-default.disabled,.ie10 .abs-action-wrap-triangle .action-default[disabled],.ie9 .abs-action-wrap-triangle .action-default.disabled,.ie9 .abs-action-wrap-triangle .action-default[disabled]{background-color:#fcfcfc;opacity:1;text-shadow:none}.abs-action-wrap-triangle-right{display:inline-block;padding-right:1.6rem;position:relative}.abs-action-wrap-triangle-right .action-default:after,.abs-action-wrap-triangle-right .action-default:before{border-color:transparent transparent transparent #e3e3e3;border-width:1.7rem 0 1.6rem 1.7rem;left:100%;margin-left:-1.7rem}.abs-action-wrap-triangle-right .action-default:before{border-left-color:#949494;right:-1px}.abs-action-wrap-triangle-right .action-default:active:after,.abs-action-wrap-triangle-right .action-default:focus:after,.abs-action-wrap-triangle-right .action-default:hover:after{border-left-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-right .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-right .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-right .action-default[disabled]:after{border-color:transparent transparent transparent #fcfcfc}.abs-action-wrap-triangle-right .action-primary:after{border-color:transparent transparent transparent #eb5202}.abs-action-wrap-triangle-right .action-primary:active:after,.abs-action-wrap-triangle-right .action-primary:focus:after,.abs-action-wrap-triangle-right .action-primary:hover:after{border-left-color:#ba4000}.abs-action-wrap-triangle-left{display:inline-block;padding-left:1.6rem}.abs-action-wrap-triangle-left .action-default{text-indent:-.85rem}.abs-action-wrap-triangle-left .action-default:after,.abs-action-wrap-triangle-left .action-default:before{border-color:transparent #e3e3e3 transparent transparent;border-width:1.7rem 1.7rem 1.6rem 0;margin-right:-1.7rem;right:100%}.abs-action-wrap-triangle-left .action-default:before{border-right-color:#949494;left:-1px}.abs-action-wrap-triangle-left .action-default:active:after,.abs-action-wrap-triangle-left .action-default:focus:after,.abs-action-wrap-triangle-left .action-default:hover:after{border-right-color:#dbdbdb}.ie10 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie10 .abs-action-wrap-triangle-left .action-default[disabled]:after,.ie9 .abs-action-wrap-triangle-left .action-default.disabled:after,.ie9 .abs-action-wrap-triangle-left .action-default[disabled]:after{border-color:transparent #fcfcfc transparent transparent}.abs-action-wrap-triangle-left .action-primary:after{border-color:transparent #eb5202 transparent transparent}.abs-action-wrap-triangle-left .action-primary:active:after,.abs-action-wrap-triangle-left .action-primary:focus:after,.abs-action-wrap-triangle-left .action-primary:hover:after{border-right-color:#ba4000}.action-default,button{background:#e3e3e3;border-color:#adadad;color:#514943}.action-default:active,.action-default:focus,.action-default:hover,button:active,button:focus,button:hover{background-color:#dbdbdb;color:#514943;text-decoration:none}.action-primary{background-color:#eb5202;border-color:#eb5202;color:#fff;text-shadow:1px 1px 0 rgba(0,0,0,.25)}.action-primary:active,.action-primary:focus,.action-primary:hover{background-color:#ba4000;border-color:#b84002;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-primary.disabled,.action-primary[disabled]{cursor:default;opacity:.5;pointer-events:none}.action-secondary{background-color:#514943;border-color:#514943;color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.3)}.action-secondary:active,.action-secondary:focus,.action-secondary:hover{background-color:#35302c;border-color:#35302c;box-shadow:0 0 0 1px #007bdb;color:#fff;text-decoration:none}.action-secondary:active{background-color:#35302c}.action-quaternary,.action-tertiary{background-color:transparent;border-color:transparent;text-shadow:none}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover,.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{background-color:transparent;border-color:transparent;box-shadow:none}.action-tertiary{color:#008bdb}.action-tertiary:active,.action-tertiary:focus,.action-tertiary:hover{color:#0fa7ff;text-decoration:underline}.action-quaternary{color:#333}.action-quaternary:active,.action-quaternary:focus,.action-quaternary:hover{color:#1a1a1a}.action-close>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-close:active{-ms-transform:scale(0.9);transform:scale(0.9)}.action-close:before{content:'\e62f';transition:color .1s linear}.action-close:hover{cursor:pointer;text-decoration:none}.abs-action-menu .action-submenu,.abs-action-menu .action-submenu .action-submenu,.action-menu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{background-color:#fff;border:1px solid #007bdb;border-radius:1px;box-shadow:1px 1px 5px rgba(0,0,0,.5);color:#333;display:none;font-weight:400;left:0;list-style:none;margin:2px 0 0;min-width:0;padding:0;position:absolute;right:0;top:100%}.abs-action-menu .action-submenu .action-submenu._active,.abs-action-menu .action-submenu._active,.action-menu .action-submenu._active,.action-menu._active,.actions-split .action-menu .action-submenu .action-submenu._active,.actions-split .action-menu .action-submenu._active,.actions-split .dropdown-menu .action-submenu .action-submenu._active,.actions-split .dropdown-menu .action-submenu._active{display:block}.abs-action-menu .action-submenu .action-submenu>li,.abs-action-menu .action-submenu>li,.action-menu .action-submenu>li,.action-menu>li,.actions-split .action-menu .action-submenu .action-submenu>li,.actions-split .action-menu .action-submenu>li,.actions-split .dropdown-menu .action-submenu .action-submenu>li,.actions-split .dropdown-menu .action-submenu>li{border:none;display:block;padding:0;transition:background-color .1s linear}.abs-action-menu .action-submenu .action-submenu>li>a:hover,.abs-action-menu .action-submenu>li>a:hover,.action-menu .action-submenu>li>a:hover,.action-menu>li>a:hover,.actions-split .action-menu .action-submenu .action-submenu>li>a:hover,.actions-split .action-menu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li>a:hover,.actions-split .dropdown-menu .action-submenu>li>a:hover{text-decoration:none}.abs-action-menu .action-submenu .action-submenu>li._visible,.abs-action-menu .action-submenu .action-submenu>li:hover,.abs-action-menu .action-submenu>li._visible,.abs-action-menu .action-submenu>li:hover,.action-menu .action-submenu>li._visible,.action-menu .action-submenu>li:hover,.action-menu>li._visible,.action-menu>li:hover,.actions-split .action-menu .action-submenu .action-submenu>li._visible,.actions-split .action-menu .action-submenu .action-submenu>li:hover,.actions-split .action-menu .action-submenu>li._visible,.actions-split .action-menu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu .action-submenu>li:hover,.actions-split .dropdown-menu .action-submenu>li._visible,.actions-split .dropdown-menu .action-submenu>li:hover{background-color:#e3e3e3}.abs-action-menu .action-submenu .action-submenu>li:active,.abs-action-menu .action-submenu>li:active,.action-menu .action-submenu>li:active,.action-menu>li:active,.actions-split .action-menu .action-submenu .action-submenu>li:active,.actions-split .action-menu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu .action-submenu>li:active,.actions-split .dropdown-menu .action-submenu>li:active{background-color:#cacaca}.abs-action-menu .action-submenu .action-submenu>li._parent,.abs-action-menu .action-submenu>li._parent,.action-menu .action-submenu>li._parent,.action-menu>li._parent,.actions-split .action-menu .action-submenu .action-submenu>li._parent,.actions-split .action-menu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent,.actions-split .dropdown-menu .action-submenu>li._parent{-webkit-flex-direction:row;display:flex;-ms-flex-direction:row;flex-direction:row}.abs-action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.abs-action-menu .action-submenu>li._parent>.action-menu-item,.action-menu .action-submenu>li._parent>.action-menu-item,.action-menu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .action-menu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu>li._parent>.action-menu-item,.actions-split .dropdown-menu .action-submenu>li._parent>.action-menu-item{min-width:100%}.abs-action-menu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .action-menu-item,.abs-action-menu .action-submenu .action-submenu .item,.abs-action-menu .action-submenu .item,.action-menu .action-menu-item,.action-menu .action-submenu .action-menu-item,.action-menu .action-submenu .item,.action-menu .item,.actions-split .action-menu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .action-menu-item,.actions-split .action-menu .action-submenu .action-submenu .item,.actions-split .action-menu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu .item,.actions-split .dropdown-menu .action-submenu .item{cursor:pointer;display:block;padding:.6875em 1em}.abs-action-menu .action-submenu .action-submenu,.action-menu .action-submenu,.actions-split .action-menu .action-submenu .action-submenu,.actions-split .dropdown-menu .action-submenu .action-submenu{bottom:auto;left:auto;margin-left:0;margin-top:-1px;position:absolute;right:auto;top:auto}.ie9 .abs-action-menu .action-submenu .action-submenu,.ie9 .abs-action-menu .action-submenu .action-submenu .action-submenu,.ie9 .action-menu .action-submenu,.ie9 .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu,.ie9 .actions-split .action-menu .action-submenu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu,.ie9 .actions-split .dropdown-menu .action-submenu .action-submenu .action-submenu{margin-left:99%;margin-top:-3.5rem}.abs-action-menu .action-submenu .action-submenu a.action-menu-item,.abs-action-menu .action-submenu a.action-menu-item,.action-menu .action-submenu a.action-menu-item,.action-menu a.action-menu-item,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .action-menu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item,.actions-split .dropdown-menu .action-submenu a.action-menu-item{color:#333}.abs-action-menu .action-submenu .action-submenu a.action-menu-item:focus,.abs-action-menu .action-submenu a.action-menu-item:focus,.action-menu .action-submenu a.action-menu-item:focus,.action-menu a.action-menu-item:focus,.actions-split .action-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .action-menu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu .action-submenu a.action-menu-item:focus,.actions-split .dropdown-menu .action-submenu a.action-menu-item:focus{background-color:#e3e3e3;box-shadow:none}.messages .message:last-child{margin:0 0 2rem}.message{background:#fffbbb;border:none;border-radius:0;color:#333;font-size:1.4rem;margin:0 0 1px;padding:1.8rem 4rem 1.8rem 5.5rem;position:relative;text-shadow:none}.message:before{background:0 0;border:0;color:#007bdb;content:'\e61a';font-family:Icons;font-size:1.9rem;font-style:normal;font-weight:400;height:auto;left:1.9rem;line-height:inherit;margin-top:-1.3rem;position:absolute;speak:none;text-shadow:none;top:50%;width:auto}.message-notice:before{color:#007bdb;content:'\e61a'}.message-warning:before{color:#eb5202;content:'\e623'}.message-error{background:#fcc}.message-error:before{color:#e22626;content:'\e632';font-size:1.5rem;left:2.2rem;margin-top:-1rem}.message-success:before{color:#79a22e;content:'\e62d'}.message-spinner:before{display:none}.message-spinner .spinner{font-size:2.5rem;left:1.5rem;position:absolute;top:1.5rem}.message-in-rating-edit{margin-left:1.8rem;margin-right:1.8rem}.modal-popup .action-close,.modal-slide .action-close{color:#736963;position:absolute;right:0;top:0;z-index:1}.modal-popup .action-close:active,.modal-slide .action-close:active{-ms-transform:none;transform:none}.modal-popup .action-close:active:before,.modal-slide .action-close:active:before{font-size:1.8rem}.modal-popup .action-close:hover:before,.modal-slide .action-close:hover:before{color:#58504b}.modal-popup .action-close:before,.modal-slide .action-close:before{font-size:2rem}.modal-popup .action-close:focus,.modal-slide .action-close:focus{background-color:transparent}.modal-popup.prompt .prompt-message{padding:2rem 0}.modal-popup.prompt .prompt-message input{width:100%}.modal-popup.confirm .modal-inner-wrap .message,.modal-popup.prompt .modal-inner-wrap .message{background:#fff}.modal-popup.modal-system-messages .modal-inner-wrap{background:#fffbbb}.modal-popup._image-box .modal-inner-wrap{margin:5rem auto;max-width:78rem;position:static}.modal-popup._image-box .thumbnail-preview{padding-bottom:3rem;text-align:center}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image-block{border:1px solid #ccc;margin:0 auto 2rem;max-width:58rem;padding:2rem}.modal-popup._image-box .thumbnail-preview .thumbnail-preview-image{max-height:54rem}.modal-popup .modal-title{font-size:2.4rem;margin-right:6.4rem}.modal-popup .modal-footer{padding-top:2.6rem;text-align:right}.modal-popup .action-close{padding:3rem}.modal-popup .action-close:active,.modal-popup .action-close:focus{background:0 0;padding-right:3.1rem;padding-top:3.1rem}.modal-slide .modal-content-new-attribute{-webkit-overflow-scrolling:touch;overflow:auto;padding-bottom:0}.modal-slide .modal-content-new-attribute iframe{margin-bottom:-2.5rem}.modal-slide .modal-title{font-size:2.1rem;margin-right:5.7rem}.modal-slide .action-close{padding:2.1rem 2.6rem}.modal-slide .action-close:active{padding-right:2.7rem;padding-top:2.2rem}.modal-slide .page-main-actions{margin-bottom:.6rem;margin-top:2.1rem}.modal-slide .magento-message{padding:0 3rem 3rem;position:relative}.modal-slide .magento-message .insert-title-inner,.modal-slide .main-col .insert-title-inner{border-bottom:1px solid #adadad;margin:0 0 2rem;padding-bottom:.5rem}.modal-slide .magento-message .insert-actions,.modal-slide .main-col .insert-actions{float:right}.modal-slide .magento-message .title,.modal-slide .main-col .title{font-size:1.6rem;padding-top:.5rem}.modal-slide .main-col,.modal-slide .side-col{float:left;padding-bottom:0}.modal-slide .main-col:after,.modal-slide .side-col:after{display:none}.modal-slide .side-col{width:20%}.modal-slide .main-col{padding-right:0;width:80%}.modal-slide .content-footer .form-buttons{float:right}.modal-title{font-weight:400;margin-bottom:0;min-height:1em}.modal-title span{font-size:1.4rem;font-style:italic;margin-left:1rem}.spinner{display:inline-block;font-size:4rem;height:1em;margin-right:1.5rem;position:relative;width:1em}.spinner>span:nth-child(1){animation-delay:.27s;-ms-transform:rotate(-315deg);transform:rotate(-315deg)}.spinner>span:nth-child(2){animation-delay:.36s;-ms-transform:rotate(-270deg);transform:rotate(-270deg)}.spinner>span:nth-child(3){animation-delay:.45s;-ms-transform:rotate(-225deg);transform:rotate(-225deg)}.spinner>span:nth-child(4){animation-delay:.54s;-ms-transform:rotate(-180deg);transform:rotate(-180deg)}.spinner>span:nth-child(5){animation-delay:.63s;-ms-transform:rotate(-135deg);transform:rotate(-135deg)}.spinner>span:nth-child(6){animation-delay:.72s;-ms-transform:rotate(-90deg);transform:rotate(-90deg)}.spinner>span:nth-child(7){animation-delay:.81s;-ms-transform:rotate(-45deg);transform:rotate(-45deg)}.spinner>span:nth-child(8){animation-delay:.9;-ms-transform:rotate(0deg);transform:rotate(0deg)}@keyframes fade{0%{background-color:#514943}100%{background-color:#fff}}.spinner>span{-ms-transform:scale(0.4);transform:scale(0.4);animation-name:fade;animation-duration:.72s;animation-iteration-count:infinite;animation-direction:linear;background-color:#fff;border-radius:6px;clip:rect(0 .28571429em .1em 0);height:.1em;margin-top:.5em;position:absolute;width:1em}.ie9 .spinner{background:url(../images/ajax-loader.gif) center no-repeat}.ie9 .spinner>span{display:none}.popup-loading{background:rgba(255,255,255,.8);border-color:#ef672f;color:#ef672f;font-size:14px;font-weight:700;left:50%;margin-left:-100px;padding:100px 0 10px;position:fixed;text-align:center;top:40%;width:200px;z-index:1003}.popup-loading:after{background-image:url(../images/loader-1.gif);content:'';height:64px;left:50%;margin:-32px 0 0 -32px;position:absolute;top:40%;width:64px;z-index:2}.loading-mask,.loading-old{background:rgba(255,255,255,.4);bottom:0;left:0;position:fixed;right:0;top:0;z-index:2003}.loading-mask img,.loading-old img{display:none}.loading-mask p,.loading-old p{margin-top:118px}.loading-mask .loader,.loading-old .loader{background:url(../images/loader-1.gif) 50% 30% no-repeat #f7f3eb;border-radius:5px;bottom:0;color:#575757;font-size:14px;font-weight:700;height:160px;left:0;margin:auto;opacity:.95;position:absolute;right:0;text-align:center;top:0;width:160px}.admin-user{float:right;line-height:1.36;margin-left:.3rem;z-index:490}.admin-user._active .admin__action-dropdown,.admin-user.active .admin__action-dropdown{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.admin-user .admin__action-dropdown{height:3.3rem;padding:.7rem 2.8rem .4rem 4rem}.admin-user .admin__action-dropdown._active:after,.admin-user .admin__action-dropdown.active:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:after{border-color:#777 transparent transparent;border-style:solid;border-width:.5rem .4rem 0;content:'';height:0;margin-top:-.2rem;position:absolute;right:1.3rem;top:50%;transition:all .2s linear;width:0}._active .admin-user .admin__action-dropdown:after,.active .admin-user .admin__action-dropdown:after{-ms-transform:rotate(180deg);transform:rotate(180deg)}.admin-user .admin__action-dropdown:hover:after{border-color:#000 transparent transparent}.admin-user .admin__action-dropdown:before{color:#777;content:'\e600';font-size:2rem;left:1.1rem;margin-top:-1.1rem;position:absolute;top:50%}.admin-user .admin__action-dropdown:hover:before{color:#333}.admin-user .admin__action-dropdown-menu{min-width:20rem;padding-left:1rem;padding-right:1rem}.admin-user .admin__action-dropdown-menu>li>a{padding-left:.5em;padding-right:1.8rem;transition:background-color .1s linear;white-space:nowrap}.admin-user .admin__action-dropdown-menu>li>a:hover{background-color:#e0f6fe;color:#333}.admin-user .admin__action-dropdown-menu>li>a:active{background-color:#c7effd;bottom:-1px;position:relative}.admin-user .admin__action-dropdown-menu .admin-user-name{text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:20rem;overflow:hidden;vertical-align:top}.admin-user-account-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;max-width:11.2rem}.search-global{float:right;margin-right:-.3rem;position:relative;z-index:480}.search-global-field{min-width:5rem}.search-global-field._active .search-global-input{background-color:#fff;border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5);padding-right:4rem;width:25rem}.search-global-field._active .search-global-action{display:block;height:3.3rem;position:absolute;right:0;text-indent:-100%;top:0;width:5rem;z-index:3}.search-global-field .autocomplete-results{height:3.3rem;position:absolute;right:0;top:0;width:25rem}.search-global-field .search-global-menu{border:1px solid #007bdb;border-top-color:transparent;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin-top:-2px;padding:0;position:absolute;right:0;top:100%;z-index:2}.search-global-field .search-global-menu:after{background-color:#fff;content:'';height:5px;left:0;position:absolute;right:0;top:-5px}.search-global-field .search-global-menu>li{background-color:#fff;border-top:1px solid #ddd;display:block;font-size:1.2rem;padding:.75rem 1.4rem .55rem}.search-global-field .search-global-menu>li._active{background-color:#e0f6fe}.search-global-field .search-global-menu .title{display:block;font-size:1.4rem}.search-global-field .search-global-menu .type{color:#1a1a1a;display:block}.search-global-label{cursor:pointer;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;z-index:2}.search-global-label:active{-ms-transform:scale(0.9);transform:scale(0.9)}.search-global-label:hover:before{color:#000}.search-global-label:before{color:#777;content:'\e60c';font-size:2rem}.search-global-input{background-color:transparent;border:1px solid transparent;font-size:1.4rem;height:3.3rem;padding:.75rem 1.4rem .55rem;position:absolute;right:0;top:0;transition:all .1s linear,width .3s linear;width:5rem;z-index:1}.search-global-action{display:none}.notifications-wrapper{float:right;line-height:1;position:relative}.notifications-wrapper.active{z-index:500}.notifications-wrapper.active .notifications-action{border-color:#007bdb;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.notifications-wrapper.active .notifications-action:after{background-color:#fff;border:none;content:'';display:block;height:6px;left:-6px;margin-top:0;position:absolute;right:0;top:100%;width:auto}.notifications-wrapper .admin__action-dropdown-menu{padding:1rem 0 0;width:32rem}.notifications-action{color:#777;height:3.3rem;padding:.75rem 2rem .65rem}.notifications-action:after{display:none}.notifications-action:before{content:'\e607';font-size:1.9rem;margin-right:0}.notifications-action:active:before{position:relative;top:1px}.notifications-action .notifications-counter{background-color:#e22626;border-radius:1em;color:#fff;display:inline-block;font-size:1.1rem;font-weight:700;left:50%;margin-left:.3em;margin-top:-1.1em;padding:.3em .5em;position:absolute;top:50%}.notifications-entry{line-height:1.36;padding:.6rem 2rem .8rem;position:relative;transition:background-color .1s linear}.notifications-entry:hover{background-color:#e0f6fe}.notifications-entry.notifications-entry-last{margin:0 2rem;padding:.3rem 0 1.3rem;text-align:center}.notifications-entry.notifications-entry-last:hover{background-color:transparent}.notifications-entry+.notifications-entry-last{border-top:1px solid #ddd;padding-bottom:.6rem}.notifications-entry ._cutted{cursor:pointer}.notifications-entry ._cutted .notifications-entry-description-start:after{content:'...'}.notifications-entry-title{color:#ef672f;display:block;font-size:1.1rem;font-weight:700;margin-bottom:.7rem;margin-right:1em}.notifications-entry-description{color:#333;font-size:1.1rem;margin-bottom:.8rem}.notifications-entry-description-end{display:none}.notifications-entry-description-end._show{display:inline}.notifications-entry-time{color:#777;font-size:1.1rem}.notifications-close{line-height:1;padding:1rem;position:absolute;right:0;top:.6rem}.notifications-close:before{color:#ccc;content:'\e620';transition:color .1s linear}.notifications-close:hover:before{color:#b3b3b3}.notifications-close:active{-ms-transform:scale(0.95);transform:scale(0.95)}.page-header-actions{padding-top:1.1rem}.page-header-hgroup{padding-right:1.5rem}.page-title{color:#333;font-size:2.8rem}.page-header{padding:1.5rem 3rem}.menu-wrapper{display:inline-block;position:relative;width:8.8rem;z-index:700}.menu-wrapper:before{background-color:#373330;bottom:0;content:'';left:0;position:fixed;top:0;width:8.8rem;z-index:699}.menu-wrapper._fixed{left:0;position:fixed;top:0}.menu-wrapper._fixed~.page-wrapper{margin-left:8.8rem}.menu-wrapper .logo{display:block;height:8.8rem;padding:2.4rem 0 2.2rem;position:relative;text-align:center;z-index:700}._keyfocus .menu-wrapper .logo:focus{background-color:#4a4542;box-shadow:none}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a{background-color:#373330}._keyfocus .menu-wrapper .logo:focus+.admin__menu .level-0:first-child>a:after{display:none}.menu-wrapper .logo:hover .logo-img{-webkit-filter:brightness(1.1);filter:brightness(1.1)}.menu-wrapper .logo:active .logo-img{-ms-transform:scale(0.95);transform:scale(0.95)}.menu-wrapper .logo .logo-img{height:4.2rem;transition:-webkit-filter .2s linear,filter .2s linear,transform .1s linear;width:3.5rem}.abs-menu-separator,.admin__menu .item-partners>a:after,.admin__menu .level-0:first-child>a:after{background-color:#736963;content:'';display:block;height:1px;left:0;margin-left:16%;position:absolute;top:0;width:68%}.admin__menu li{display:block}.admin__menu .level-0:first-child>a{position:relative}.admin__menu .level-0._active>a,.admin__menu .level-0:hover>a{color:#f7f3eb}.admin__menu .level-0._active>a{background-color:#524d49}.admin__menu .level-0:hover>a{background-color:#4a4542}.admin__menu .level-0>a{color:#aaa6a0;display:block;font-size:1rem;letter-spacing:.025em;min-height:6.2rem;padding:1.2rem .5rem .5rem;position:relative;text-align:center;text-decoration:none;text-transform:uppercase;transition:background-color .1s linear;word-wrap:break-word;z-index:700}.admin__menu .level-0>a:focus{box-shadow:none}.admin__menu .level-0>a:before{content:'\e63a';display:block;font-size:2.2rem;height:2.2rem}.admin__menu .level-0>.submenu{background-color:#4a4542;box-shadow:0 0 3px #000;left:100%;min-height:calc(8.8rem + 2rem + 100%);padding:2rem 0 0;position:absolute;top:0;-ms-transform:translateX(-100%);transform:translateX(-100%);transition-duration:.3s;transition-property:transform,visibility;transition-timing-function:ease-in-out;visibility:hidden;z-index:697}.ie10 .admin__menu .level-0>.submenu,.ie11 .admin__menu .level-0>.submenu{height:100%}.admin__menu .level-0._show>.submenu{-ms-transform:translateX(0);transform:translateX(0);visibility:visible;z-index:698}.admin__menu .level-1{margin-left:1.5rem;margin-right:1.5rem}.admin__menu [class*=level-]:not(.level-0) a{display:block;padding:1.25rem 1.5rem}.admin__menu [class*=level-]:not(.level-0) a:hover{background-color:#403934}.admin__menu [class*=level-]:not(.level-0) a:active{background-color:#322c29;padding-bottom:1.15rem;padding-top:1.35rem}.admin__menu .submenu li{min-width:23.8rem}.admin__menu .submenu a{color:#fcfcfc;transition:background-color .1s linear}.admin__menu .submenu a:focus,.admin__menu .submenu a:hover{box-shadow:none;text-decoration:none}._keyfocus .admin__menu .submenu a:focus{background-color:#403934}._keyfocus .admin__menu .submenu a:active{background-color:#322c29}.admin__menu .submenu .parent{margin-bottom:4.5rem}.admin__menu .submenu .parent .submenu-group-title{color:#a79d95;display:block;font-size:1.6rem;font-weight:600;margin-bottom:.7rem;padding:1.25rem 1.5rem;pointer-events:none}.admin__menu .submenu .column{display:table-cell}.admin__menu .submenu-title{color:#fff;display:block;font-size:2.2rem;font-weight:600;margin-bottom:4.2rem;margin-left:3rem;margin-right:5.8rem}.admin__menu .submenu-sub-title{color:#fff;display:block;font-size:1.2rem;margin:-3.8rem 5.8rem 3.8rem 3rem}.admin__menu .action-close{padding:2.4rem 2.8rem;position:absolute;right:0;top:0}.admin__menu .action-close:before{color:#a79d95;font-size:1.7rem}.admin__menu .action-close:hover:before{color:#fff}.admin__menu .item-dashboard>a:before{content:'\e604';font-size:1.8rem;padding-top:.4rem}.admin__menu .item-sales>a:before{content:'\e60b'}.admin__menu .item-catalog>a:before{content:'\e608'}.admin__menu .item-customer>a:before{content:'\e603';font-size:2.6rem;position:relative;top:-.4rem}.admin__menu .item-marketing>a:before{content:'\e609';font-size:2rem;padding-top:.2rem}.admin__menu .item-content>a:before{content:'\e602';font-size:2.4rem;position:relative;top:-.2rem}.admin__menu .item-report>a:before{content:'\e60a'}.admin__menu .item-stores>a:before{content:'\e60d';font-size:1.9rem;padding-top:.3rem}.admin__menu .item-system>a:before{content:'\e610'}.admin__menu .item-partners._active>a:after,.admin__menu .item-system._current+.item-partners>a:after{display:none}.admin__menu .item-partners>a{padding-bottom:1rem}.admin__menu .item-partners>a:before{content:'\e612'}.admin__menu .level-0>.submenu>ul>.level-1:only-of-type>.submenu-group-title,.admin__menu .submenu .column:only-of-type .submenu-group-title{display:none}.admin__menu-overlay{bottom:0;left:0;position:fixed;right:0;top:0;z-index:697}.store-switcher{color:#333;float:left;font-size:1.3rem;margin-top:.7rem}.store-switcher .admin__action-dropdown{background-color:#f8f8f8;margin-left:.5em}.store-switcher .dropdown{display:inline-block;position:relative}.store-switcher .dropdown:after,.store-switcher .dropdown:before{content:'';display:table}.store-switcher .dropdown:after{clear:both}.store-switcher .dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e607';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle:active:after,.store-switcher .dropdown .action.toggle:hover:after{color:#333}.store-switcher .dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .dropdown .action.toggle.active:after{-webkit-font-smoothing:antialiased;font-size:22px;line-height:2;color:#333;content:'\e618';font-family:icons-blank-theme;margin:0;vertical-align:top;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.store-switcher .dropdown .action.toggle.active:active:after,.store-switcher .dropdown .action.toggle.active:hover:after{color:#333}.store-switcher .dropdown .dropdown-menu{margin:4px 0 0;padding:0;list-style:none;background:#fff;border:1px solid #aaa6a0;min-width:19.5rem;z-index:100;box-sizing:border-box;display:none;position:absolute;top:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5)}.store-switcher .dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .dropdown .dropdown-menu li:hover{background:0 0;cursor:pointer}.store-switcher .dropdown.active{overflow:visible}.store-switcher .dropdown.active .dropdown-menu{display:block}.store-switcher .dropdown-menu{left:0;margin-top:.5em;max-height:250px;overflow-y:auto;padding-top:.25em}.store-switcher .dropdown-menu li{border:0;cursor:default}.store-switcher .dropdown-menu li:hover{cursor:default}.store-switcher .dropdown-menu li a,.store-switcher .dropdown-menu li span{color:#333;display:block;padding:.5rem 1.3rem}.store-switcher .dropdown-menu li a{text-decoration:none}.store-switcher .dropdown-menu li a:hover{background:#e9e9e9}.store-switcher .dropdown-menu li span{color:#adadad;cursor:default}.store-switcher .dropdown-menu li.current span{background:#eee;color:#333}.store-switcher .dropdown-menu .store-switcher-store a,.store-switcher .dropdown-menu .store-switcher-store span{padding-left:2.6rem}.store-switcher .dropdown-menu .store-switcher-store-view a,.store-switcher .dropdown-menu .store-switcher-store-view span{padding-left:3.9rem}.store-switcher .dropdown-menu .dropdown-toolbar{border-top:1px solid #ebebeb;margin-top:1rem}.store-switcher .dropdown-menu .dropdown-toolbar a:before{content:'\e610';margin-right:.25em;position:relative;top:1px}.store-switcher-label{font-weight:700}.store-switcher-alt{display:inline-block;position:relative}.store-switcher-alt.active .dropdown-menu{display:block}.store-switcher-alt .dropdown-menu{margin-top:2px;white-space:nowrap}.store-switcher-alt .dropdown-menu ul{list-style:none;margin:0;padding:0}.store-switcher-alt strong{color:#a79d95;display:block;font-size:14px;font-weight:500;line-height:1.333;padding:5px 10px}.store-switcher-alt .store-selected{color:#676056;cursor:pointer;font-size:12px;font-weight:400;line-height:1.333}.store-switcher-alt .store-selected:after{-webkit-font-smoothing:antialiased;color:#afadac;content:'\e02c';font-style:normal;font-weight:400;margin:0 0 0 3px;speak:none;vertical-align:text-top}.store-switcher-alt .store-switcher-store,.store-switcher-alt .store-switcher-website{padding:0}.store-switcher-alt .store-switcher-store:hover,.store-switcher-alt .store-switcher-website:hover{background:0 0}.store-switcher-alt .manage-stores,.store-switcher-alt .store-switcher-all,.store-switcher-alt .store-switcher-store-view{padding:0}.store-switcher-alt .manage-stores>a,.store-switcher-alt .store-switcher-all>a{color:#676056;display:block;font-size:12px;padding:8px 15px;text-decoration:none}.store-switcher-website{margin:5px 0 0}.store-switcher-website>strong{padding-left:13px}.store-switcher-store{margin:1px 0 0}.store-switcher-store>strong{padding-left:20px}.store-switcher-store>ul{margin-top:1px}.store-switcher-store-view:first-child{border-top:1px solid #e5e5e5}.store-switcher-store-view>a{color:#333;display:block;font-size:13px;padding:5px 15px 5px 24px;text-decoration:none}.store-view:not(.store-switcher){float:left}.store-view .store-switcher-label{display:inline-block;margin-top:1rem}.tooltip{margin-left:.5em}.tooltip .help a,.tooltip .help span{cursor:pointer;display:inline-block;height:22px;position:relative;vertical-align:middle;width:22px;z-index:2}.tooltip .help a:before,.tooltip .help span:before{color:#333;content:'\e633';font-size:1.7rem}.tooltip .help a:hover{text-decoration:none}.tooltip .tooltip-content{background:#000;border-radius:3px;color:#fff;display:none;margin-left:-19px;margin-top:10px;max-width:200px;padding:4px 8px;position:absolute;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{border-bottom:5px solid #000;border-left:5px solid transparent;border-right:5px solid transparent;content:'';height:0;left:20px;opacity:.8;position:absolute;top:-5px;width:0}.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}.page-actions._fixed,.page-main-actions:not(._hidden){background:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;padding:1.5rem}.page-main-actions{margin:0 0 3rem}.page-main-actions._hidden .store-switcher{display:none}.page-main-actions._hidden .page-actions-placeholder{min-height:50px}.page-actions{float:right}.page-main-actions .page-actions._fixed{left:8.8rem;position:fixed;right:0;top:0;z-index:501}.page-main-actions .page-actions._fixed .page-actions-inner:before{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#333;content:attr(data-title);float:left;font-size:2.8rem;margin-top:.3rem;max-width:50%}.page-actions .page-actions-buttons>button,.page-actions>button{float:right;margin-left:1.3rem}.page-actions .page-actions-buttons>button.action-back,.page-actions .page-actions-buttons>button.back,.page-actions>button.action-back,.page-actions>button.back{float:left;-ms-flex-order:-1;order:-1}.page-actions .page-actions-buttons>button.action-back:before,.page-actions .page-actions-buttons>button.back:before,.page-actions>button.action-back:before,.page-actions>button.back:before{content:'\e626';margin-right:.5em;position:relative;top:1px}.page-actions .page-actions-buttons>button.action-primary,.page-actions .page-actions-buttons>button.primary,.page-actions>button.action-primary,.page-actions>button.primary{-ms-flex-order:2;order:2}.page-actions .page-actions-buttons>button.save:not(.primary),.page-actions>button.save:not(.primary){-ms-flex-order:1;order:1}.page-actions .page-actions-buttons>button.delete,.page-actions>button.delete{-ms-flex-order:-1;order:-1}.page-actions .actions-split{float:right;margin-left:1.3rem;-ms-flex-order:2;order:2}.page-actions .actions-split .dropdown-menu .item{display:block}.page-actions-buttons{float:right;-ms-flex-pack:end;justify-content:flex-end;display:-ms-flexbox;display:flex}.customer-index-edit .page-actions-buttons{background-color:transparent}.admin__page-nav{background:#f1f1f1;border:1px solid #e3e3e3}.admin__page-nav._collapsed:first-child{border-bottom:none}.admin__page-nav._collapsed._show{border-bottom:1px solid #e3e3e3}.admin__page-nav._collapsed._show ._collapsible{background:#f1f1f1}.admin__page-nav._collapsed._show ._collapsible:after{content:'\e62b'}.admin__page-nav._collapsed._show ._collapsible+.admin__page-nav-items{display:block}.admin__page-nav._collapsed._hide .admin__page-nav-title-messages,.admin__page-nav._collapsed._hide .admin__page-nav-title-messages ._active{display:inline-block}.admin__page-nav+._collapsed{border-bottom:none;border-top:none}.admin__page-nav-title{border-bottom:1px solid #e3e3e3;color:#303030;display:block;font-size:1.4rem;line-height:1.2;margin:0 0 -1px;padding:1.8rem 1.5rem;position:relative;text-transform:uppercase}.admin__page-nav-title._collapsible{background:#fff;cursor:pointer;margin:0;padding-right:3.5rem;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-title._collapsible+.admin__page-nav-items{display:none;margin-top:-1px}.admin__page-nav-title._collapsible:after{content:'\e628';font-size:1.3rem;font-weight:700;position:absolute;right:1.8rem;top:2rem}.admin__page-nav-title._collapsible:hover{background:#f1f1f1}.admin__page-nav-title._collapsible:last-child{margin:0 0 -1px}.admin__page-nav-title strong{font-weight:700}.admin__page-nav-title .admin__page-nav-title-messages{display:none}.admin__page-nav-items{list-style-type:none;margin:0;padding:1rem 0 1.3rem}.admin__page-nav-item{border-left:3px solid transparent;margin-left:.7rem;padding:0;position:relative;transition:border-color .1s ease-out,background-color .1s ease-out}.admin__page-nav-item:hover{border-color:#e4e4e4}.admin__page-nav-item:hover .admin__page-nav-link{background:#e4e4e4;color:#303030;text-decoration:none}.admin__page-nav-item._active,.admin__page-nav-item.ui-state-active{border-color:#eb5202}.admin__page-nav-item._active .admin__page-nav-link,.admin__page-nav-item.ui-state-active .admin__page-nav-link{background:#fff;border-color:#e3e3e3;border-right:1px solid #fff;color:#303030;margin-right:-1px;font-weight:600}.admin__page-nav-item._loading:before,.admin__page-nav-item.ui-tabs-loading:before{display:none}.admin__page-nav-item._loading .admin__page-nav-item-message-loader,.admin__page-nav-item.ui-tabs-loading .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-link{border:1px solid transparent;border-width:1px 0;color:#303030;display:block;font-weight:500;line-height:1.2;margin:0 0 -1px;padding:2rem 4rem 2rem 1rem;transition:border-color .1s ease-out,background-color .1s ease-out;word-wrap:break-word}.admin__page-nav-item-messages{display:inline-block}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-size:1.4rem;font-weight:400;left:-1rem;line-height:1.36;padding:1.5rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after,.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}.admin__page-nav-item-messages .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf;margin-top:1px}.admin__page-nav-item-message-loader{display:none;margin-top:-1rem;position:absolute;right:0;top:50%}.admin__page-nav-item-message-loader .spinner{font-size:2rem;margin-right:1.5rem}._loading>.admin__page-nav-item-messages .admin__page-nav-item-message-loader{display:inline-block}.admin__page-nav-item-message{position:relative}.admin__page-nav-item-message:hover{z-index:500}.admin__page-nav-item-message:hover .admin__page-nav-item-message-tooltip{display:block}.admin__page-nav-item-message._changed,.admin__page-nav-item-message._error{display:none}.admin__page-nav-item-message .admin__page-nav-item-message-icon{display:inline-block;font-size:1.4rem;padding-left:.8em;vertical-align:baseline}.admin__page-nav-item-message .admin__page-nav-item-message-icon:after{color:#666;content:'\e631'}._changed:not(._error)>.admin__page-nav-item-messages ._changed{display:inline-block}._error .admin__page-nav-item-message-icon:after{color:#eb5202;content:'\e623'}._error>.admin__page-nav-item-messages ._error{display:inline-block}._error>.admin__page-nav-item-messages ._error .spinner{font-size:2rem;margin-right:1.5rem}._error .admin__page-nav-item-message-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:3.7rem;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;left:-1rem;line-height:1.36;padding:2rem;position:absolute;text-transform:none;width:27rem;word-break:normal;z-index:2}._error .admin__page-nav-item-message-tooltip:after,._error .admin__page-nav-item-message-tooltip:before{border:15px solid transparent;height:0;width:0;border-top-color:#f1f1f1;content:'';display:block;left:2rem;position:absolute;top:100%;z-index:3}._error .admin__page-nav-item-message-tooltip:after{border-top-color:#f1f1f1;margin-top:-1px;z-index:4}._error .admin__page-nav-item-message-tooltip:before{border-top-color:#bfbfbf}.admin__data-grid-wrap-static .data-grid{box-sizing:border-box}.admin__data-grid-wrap-static .data-grid thead{color:#333}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td{background-color:#f5f5f5}.admin__data-grid-wrap-static .data-grid tr:nth-child(even) td._dragging{background-color:rgba(245,245,245,.95)}.admin__data-grid-wrap-static .data-grid ul{margin-left:1rem;padding-left:1rem}.admin__data-grid-wrap-static .admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-wrap-static .admin__data-grid-loading-mask .grid-loader{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-filters-actions-wrap{float:right}.data-grid-search-control-wrap{float:left;max-width:45.5rem;position:relative;width:35%}.data-grid-search-control-wrap :-ms-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-webkit-input-placeholder{font-style:italic}.data-grid-search-control-wrap ::-moz-placeholder{font-style:italic}.data-grid-search-control-wrap .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:.6rem 2rem .2rem;position:absolute;right:0;top:1px}.data-grid-search-control-wrap .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.data-grid-search-control-wrap .action-submit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.data-grid-search-control-wrap .action-submit:hover:before{color:#1a1a1a}._keyfocus .data-grid-search-control-wrap .action-submit:focus{box-shadow:0 0 0 1px #008bdb}.data-grid-search-control-wrap .action-submit:before{content:'\e60c';font-size:2rem;transition:color .1s linear}.data-grid-search-control-wrap .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.data-grid-search-control-wrap .abs-action-menu .action-submenu,.data-grid-search-control-wrap .abs-action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .action-menu,.data-grid-search-control-wrap .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu,.data-grid-search-control-wrap .actions-split .action-menu .action-submenu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu,.data-grid-search-control-wrap .actions-split .dropdown-menu .action-submenu .action-submenu{max-height:19.25rem;overflow-y:auto;z-index:398}.data-grid-search-control-wrap .action-menu-item._selected{background-color:#e0f6fe}.data-grid-search-control-wrap .data-grid-search-label{display:none}.data-grid-search-control{padding-right:6rem;width:100%}.data-grid-filters-action-wrap{float:left;padding-left:2rem}.data-grid-filters-action-wrap .action-default{font-size:1.3rem;margin-bottom:1rem;padding-left:1.7rem;padding-right:2.1rem;padding-top:.7rem}.data-grid-filters-action-wrap .action-default._active{background-color:#fff;border-bottom-color:#fff;border-right-color:#ccc;font-weight:600;margin:-.1rem 0 0;padding-bottom:1.6rem;padding-top:.8rem;position:relative;z-index:281}.data-grid-filters-action-wrap .action-default._active:after{background-color:#eb5202;bottom:100%;content:'';height:3px;left:-1px;position:absolute;right:-1px}.data-grid-filters-action-wrap .action-default:before{color:#333;content:'\e605';font-size:1.8rem;margin-right:.4rem;position:relative;top:-1px;vertical-align:top}.data-grid-filters-action-wrap .filters-active{display:none}.admin__action-grid-select .admin__control-select{margin:-.5rem .5rem 0 0;padding-bottom:.6rem;padding-top:.6rem}.admin__data-grid-filters-wrap{opacity:0;visibility:hidden;clear:both;font-size:1.3rem;transition:opacity .3s ease}.admin__data-grid-filters-wrap._show{opacity:1;visibility:visible;border-bottom:1px solid #ccc;border-top:1px solid #ccc;margin-bottom:.7rem;padding:3.6rem 0 3rem;position:relative;top:-1px;z-index:280}.admin__data-grid-filters-wrap._show .admin__data-grid-filters,.admin__data-grid-filters-wrap._show .admin__data-grid-filters-footer{display:block}.admin__data-grid-filters-wrap .admin__form-field-label,.admin__data-grid-filters-wrap .admin__form-field-legend{display:block;font-weight:700;margin:0 0 .3rem;text-align:left}.admin__data-grid-filters-wrap .admin__form-field{display:inline-block;margin-bottom:2em;margin-left:0;padding-left:2rem;padding-right:2rem;vertical-align:top;width:calc(100% / 4 - 4px)}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field{display:block;float:none;margin-bottom:1.5rem;padding-left:0;padding-right:0;width:auto}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field:last-child{margin-bottom:0}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-label{border:1px solid transparent;float:left;font-weight:400;line-height:1.36;margin-bottom:0;padding-bottom:.6rem;padding-right:1em;padding-top:.6rem;width:25%}.admin__data-grid-filters-wrap .admin__form-field .admin__form-field .admin__form-field-control{margin-left:25%}.admin__data-grid-filters-wrap .admin__action-multiselect,.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text,.admin__data-grid-filters-wrap .admin__form-field-label{font-size:1.3rem}.admin__data-grid-filters-wrap .admin__control-select{height:3.2rem;padding-top:.5rem}.admin__data-grid-filters-wrap .admin__action-multiselect:before{height:3.2rem;width:3.2rem}.admin__data-grid-filters-wrap .admin__control-select,.admin__data-grid-filters-wrap .admin__control-text._has-datepicker{width:100%}.admin__data-grid-filters{display:none;margin-left:-2rem;margin-right:-2rem}.admin__filters-legend{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-filters-footer{display:none;font-size:1.4rem}.admin__data-grid-filters-footer .admin__footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-filters-footer .admin__footer-secondary-actions{float:left;width:50%}.admin__data-grid-filters-current{border-bottom:.1rem solid #ccc;border-top:.1rem solid #ccc;display:none;font-size:1.3rem;margin-bottom:.9rem;padding-bottom:.8rem;padding-top:1.1rem;width:100%}.admin__data-grid-filters-current._show{display:table;position:relative;top:-1px;z-index:3}.admin__data-grid-filters-current._show+.admin__data-grid-filters-wrap._show{margin-top:-1rem}.admin__current-filters-actions-wrap,.admin__current-filters-list-wrap,.admin__current-filters-title-wrap{display:table-cell;vertical-align:top}.admin__current-filters-title{margin-right:1em;white-space:nowrap}.admin__current-filters-list-wrap{width:100%}.admin__current-filters-list{margin-bottom:0}.admin__current-filters-list>li{display:inline-block;font-weight:600;margin:0 1rem .5rem;padding-right:2.6rem;position:relative}.admin__current-filters-list .action-remove{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;padding:0;line-height:1;position:absolute;right:0;top:1px}.admin__current-filters-list .action-remove:hover{background-color:transparent;border:none;box-shadow:none}.admin__current-filters-list .action-remove:hover:before{color:#949494}.admin__current-filters-list .action-remove:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__current-filters-list .action-remove:before{color:#adadad;content:'\e620';font-size:1.6rem;transition:color .1s linear}.admin__current-filters-list .action-remove>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__current-filters-actions-wrap .action-clear{border:none;padding-bottom:0;padding-top:0;white-space:nowrap}.admin__data-grid-pager-wrap{float:right;text-align:right}.admin__data-grid-pager{display:inline-block;margin-left:3rem}.admin__data-grid-pager .admin__control-text::-webkit-inner-spin-button,.admin__data-grid-pager .admin__control-text::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.admin__data-grid-pager .admin__control-text{-moz-appearance:textfield;text-align:center;width:4.4rem}.action-next,.action-previous{width:4.4rem}.action-next:before,.action-previous:before{font-weight:700}.action-next>span,.action-previous>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.action-previous{margin-right:2.5rem;text-indent:-.25em}.action-previous:before{content:'\e629'}.action-next{margin-left:1.5rem;text-indent:.1em}.action-next:before{content:'\e62a'}.admin__data-grid-action-bookmarks{opacity:.98}.admin__data-grid-action-bookmarks .admin__action-dropdown-text:after{left:0;right:-6px}.admin__data-grid-action-bookmarks._active{z-index:290}.admin__data-grid-action-bookmarks .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:15rem;min-width:4.9rem;vertical-align:top;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown:before{content:'\e60f'}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu{font-size:1.3rem;left:0;padding:1rem 0;right:auto}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li{padding:0 5rem 0 0;position:relative;white-space:nowrap}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action){transition:background-color .1s linear}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu>li:not(.action-dropdown-menu-action):hover{background-color:#e3e3e3}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item{max-width:23rem;min-width:18rem;white-space:normal;word-break:break-all}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit{display:none;padding-bottom:1rem;padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-edit .action-dropdown-menu-item-actions{padding-bottom:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action{padding-left:1rem;padding-top:1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action+.action-dropdown-menu-item-last{padding-top:.5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a{color:#008bdb;text-decoration:none;display:inline-block;padding-left:1.1rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-action>a:hover{color:#0fa7ff;text-decoration:underline}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-last{padding-bottom:0}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item{display:none}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._edit .action-dropdown-menu-item-edit{display:block}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu ._active .action-dropdown-menu-link{font-weight:600}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{font-size:1.3rem;min-width:15rem;width:calc(100% - 4rem)}.ie9 .admin__data-grid-action-bookmarks .admin__action-dropdown-menu .admin__control-text{width:15rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-item-actions{border-left:1px solid #fff;bottom:0;position:absolute;right:0;top:0;width:5rem}.admin__data-grid-action-bookmarks .admin__action-dropdown-menu .action-dropdown-menu-link{color:#333;display:block;text-decoration:none;padding:1rem 1rem 1rem 2.1rem}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit,.admin__data-grid-action-bookmarks .action-submit{background-color:transparent;border:none;border-radius:0;box-shadow:none;margin:0;vertical-align:top}.admin__data-grid-action-bookmarks .action-delete:hover,.admin__data-grid-action-bookmarks .action-edit:hover,.admin__data-grid-action-bookmarks .action-submit:hover{background-color:transparent;border:none;box-shadow:none}.admin__data-grid-action-bookmarks .action-delete:before,.admin__data-grid-action-bookmarks .action-edit:before,.admin__data-grid-action-bookmarks .action-submit:before{font-size:1.7rem}.admin__data-grid-action-bookmarks .action-delete>span,.admin__data-grid-action-bookmarks .action-edit>span,.admin__data-grid-action-bookmarks .action-submit>span{clip:rect(0,0,0,0);overflow:hidden;position:absolute}.admin__data-grid-action-bookmarks .action-delete,.admin__data-grid-action-bookmarks .action-edit{padding:.6rem 1.4rem}.admin__data-grid-action-bookmarks .action-delete:active,.admin__data-grid-action-bookmarks .action-edit:active{-ms-transform:scale(0.9);transform:scale(0.9)}.admin__data-grid-action-bookmarks .action-submit{padding:.6rem 1rem .6rem .8rem}.admin__data-grid-action-bookmarks .action-submit:active{position:relative;right:-1px}.admin__data-grid-action-bookmarks .action-submit:before{content:'\e625'}.admin__data-grid-action-bookmarks .action-delete:before{content:'\e630'}.admin__data-grid-action-bookmarks .action-edit{padding-top:.8rem}.admin__data-grid-action-bookmarks .action-edit:before{content:'\e631'}.admin__data-grid-action-columns._active{opacity:.98;z-index:290}.admin__data-grid-action-columns .admin__action-dropdown:before{content:'\e610';font-size:1.8rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-columns-menu{color:#303030;font-size:1.3rem;overflow:hidden;padding:2.2rem 3.5rem 1rem;z-index:1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-header{border-bottom:1px solid #d1d1d1}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-content{width:49.2rem}.admin__data-grid-action-columns-menu._overflow .admin__action-dropdown-menu-footer{border-top:1px solid #d1d1d1;padding-top:2.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-content{max-height:22.85rem;overflow-y:auto;padding-top:1.5rem;position:relative;width:47.4rem}.admin__data-grid-action-columns-menu .admin__field-option{float:left;height:1.9rem;margin-bottom:1.5rem;padding:0 1rem 0 0;width:15.8rem}.admin__data-grid-action-columns-menu .admin__field-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-header{padding-bottom:1.5rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-menu-footer{padding:1rem 0 2rem}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-main-actions{margin-left:25%;text-align:right}.admin__data-grid-action-columns-menu .admin__action-dropdown-footer-secondary-actions{float:left;margin-left:-1em}.admin__data-grid-action-export._active{opacity:.98;z-index:290}.admin__data-grid-action-export .admin__action-dropdown:before{content:'\e635';font-size:1.7rem;left:.3rem;margin-right:.7rem;vertical-align:top}.admin__data-grid-action-export-menu{padding-left:2rem;padding-right:2rem;padding-top:1rem}.admin__data-grid-action-export-menu .admin__action-dropdown-footer-main-actions{padding-bottom:2rem;padding-top:2.5rem;white-space:nowrap}.sticky-header{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:8.8rem;margin-top:-1px;padding:.5rem 3rem 0;position:fixed;right:0;top:77px;z-index:398}.sticky-header .admin__data-grid-wrap{margin-bottom:0;overflow-x:visible;padding-bottom:0}.sticky-header .admin__data-grid-header-row{position:relative;text-align:right}.sticky-header .admin__data-grid-header-row:last-child{margin:0}.sticky-header .admin__data-grid-actions-wrap,.sticky-header .admin__data-grid-filters-wrap,.sticky-header .admin__data-grid-pager-wrap,.sticky-header .data-grid-filters-actions-wrap,.sticky-header .data-grid-search-control-wrap{display:inline-block;float:none;vertical-align:top}.sticky-header .action-select-wrap{float:left;margin-right:1.5rem;width:16.66666667%}.sticky-header .admin__control-support-text{float:left}.sticky-header .data-grid-search-control-wrap{margin:-.5rem 0 0 1.1rem;width:auto}.sticky-header .data-grid-search-control-wrap .data-grid-search-label{box-sizing:border-box;cursor:pointer;display:block;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;position:relative;text-align:center}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:before{color:#333;content:'\e60c';font-size:2rem;transition:color .1s linear}.sticky-header .data-grid-search-control-wrap .data-grid-search-label:hover:before{color:#000}.sticky-header .data-grid-search-control-wrap .data-grid-search-label span{display:none}.sticky-header .data-grid-filters-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-left:0;position:relative}.sticky-header .data-grid-filters-actions-wrap .action-default{background-color:transparent;border:1px solid transparent;box-sizing:border-box;min-width:3.8rem;padding:1.2rem .6rem 1.7rem;text-align:center;transition:all .15s ease}.sticky-header .data-grid-filters-actions-wrap .action-default span{display:none}.sticky-header .data-grid-filters-actions-wrap .action-default:before{margin:0}.sticky-header .data-grid-filters-actions-wrap .action-default._active{background-color:#fff;border-color:#adadad #adadad #fff;box-shadow:1px 1px 5px rgba(0,0,0,.5);z-index:210}.sticky-header .data-grid-filters-actions-wrap .action-default._active:after{background-color:#fff;content:'';height:6px;left:-2px;position:absolute;right:-6px;top:100%}.sticky-header .data-grid-filters-action-wrap{padding:0}.sticky-header .admin__data-grid-filters-wrap{background-color:#fff;border:1px solid #adadad;box-shadow:0 5px 5px 0 rgba(0,0,0,.25);left:0;padding-left:3.5rem;padding-right:3.5rem;position:absolute;top:100%;width:100%;z-index:209}.sticky-header .admin__data-grid-filters-current+.admin__data-grid-filters-wrap._show{margin-top:-6px}.sticky-header .filters-active{background-color:#e04f00;border-radius:10px;color:#fff;display:block;font-size:1.4rem;font-weight:700;padding:.1rem .7rem;position:absolute;right:-7px;top:0;z-index:211}.sticky-header .filters-active:empty{padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-actions-wrap{margin:-.5rem 0 0 1.1rem;padding-right:.3rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown{background-color:transparent;box-sizing:border-box;min-width:3.8rem;padding-left:.6rem;padding-right:.6rem;text-align:center}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown .admin__action-dropdown-text{display:inline-block;max-width:0;min-width:0;overflow:hidden}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:before{margin:0}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap{margin-right:1.1rem}.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after,.sticky-header .admin__data-grid-actions-wrap .admin__action-dropdown:after{display:none}.sticky-header .admin__data-grid-actions-wrap ._active .admin__action-dropdown{background-color:#fff}.sticky-header .admin__data-grid-action-bookmarks .admin__action-dropdown:before{position:relative;top:-3px}.sticky-header .admin__data-grid-filters-current{border-bottom:0;border-top:0;margin-bottom:0;padding-bottom:0;padding-top:0}.sticky-header .admin__data-grid-pager .admin__control-text,.sticky-header .admin__data-grid-pager-wrap .admin__control-support-text,.sticky-header .data-grid-search-control-wrap .action-submit,.sticky-header .data-grid-search-control-wrap .data-grid-search-control{display:none}.sticky-header .action-next{margin:0}.sticky-header .data-grid{margin-bottom:-1px}.data-grid-cap-left,.data-grid-cap-right{background-color:#f8f8f8;bottom:-2px;position:absolute;top:6rem;width:3rem;z-index:201}.data-grid-cap-left{left:0}.admin__data-grid-header{font-size:1.4rem}.admin__data-grid-header-row+.admin__data-grid-header-row{margin-top:1.1rem}.admin__data-grid-header-row:last-child{margin-bottom:0}.admin__data-grid-header-row .action-select-wrap{display:block}.admin__data-grid-header-row .action-select{width:100%}.admin__data-grid-actions-wrap{float:right;margin-left:1.1rem;margin-top:-.5rem;text-align:right}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap{position:relative;text-align:left;vertical-align:middle}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._hide+.admin__action-dropdown-wrap:after,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:first-child:after{display:none}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown,.admin__data-grid-actions-wrap .admin__action-dropdown-wrap._active .admin__action-dropdown-menu{border-color:#adadad}.admin__data-grid-actions-wrap .admin__action-dropdown-wrap:after{border-left:1px solid #ccc;content:'';height:3.2rem;left:0;position:absolute;top:.5rem;z-index:3}.admin__data-grid-actions-wrap .admin__action-dropdown{padding-bottom:1.7rem;padding-top:1.2rem}.admin__data-grid-actions-wrap .admin__action-dropdown:after{margin-top:-.4rem}.admin__data-grid-outer-wrap{min-height:8rem;position:relative}.admin__data-grid-wrap{margin-bottom:2rem;max-width:100%;overflow-x:auto;padding-bottom:1rem;padding-top:2rem}.admin__data-grid-loading-mask{background:rgba(255,255,255,.5);bottom:0;left:0;position:absolute;right:0;top:0;z-index:399}.admin__data-grid-loading-mask .spinner{font-size:4rem;left:50%;margin-left:-2rem;margin-top:-2rem;position:absolute;top:50%}.ie9 .admin__data-grid-loading-mask .spinner{background:url(../images/loader-2.gif) 50% 50% no-repeat;bottom:0;height:149px;left:0;margin:auto;position:absolute;right:0;top:0;width:218px}.data-grid-cell-content{display:inline-block;overflow:hidden;width:100%}body._in-resize{cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body._in-resize *,body._in-resize .data-grid-th,body._in-resize .data-grid-th._draggable,body._in-resize .data-grid-th._sortable{cursor:col-resize!important}._layout-fixed{table-layout:fixed}.data-grid{border:none;font-size:1.3rem;margin-bottom:0;width:100%}.data-grid:not(._dragging-copy) ._odd-row td._dragging{background-color:#d0d0d0}.data-grid:not(._dragging-copy) ._dragging{background-color:#d9d9d9;color:rgba(48,48,48,.95)}.data-grid:not(._dragging-copy) ._dragging a{color:rgba(0,139,219,.95)}.data-grid:not(._dragging-copy) ._dragging a:hover{color:rgba(15,167,255,.95)}.data-grid._dragged{outline:#007bdb solid 1px}.data-grid thead{background-color:transparent}.data-grid tfoot th{padding:1rem}.data-grid tr._odd-row td{background-color:#f5f5f5}.data-grid tr._odd-row td._update-status-active{background:#89e1ff}.data-grid tr._odd-row td._update-status-upcoming{background:#b7ee63}.data-grid tr:hover td._update-status-active,.data-grid tr:hover td._update-status-upcoming{background-color:#e5f7fe}.data-grid tr.data-grid-tr-no-data td{font-size:1.6rem;padding:3rem;text-align:center}.data-grid tr.data-grid-tr-no-data:hover td{background-color:#fff;cursor:default}.data-grid tr:active td{background-color:#e0f6fe}.data-grid tr:hover td{background-color:#e5f7fe}.data-grid tr._dragged td{background:#d0d0d0}.data-grid tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.data-grid tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.data-grid tr:not(.data-grid-editable-row):last-child td{border-bottom:.1rem solid #d6d6d6}.data-grid tr ._clickable,.data-grid tr._clickable{cursor:pointer}.data-grid tr._disabled{pointer-events:none}.data-grid td,.data-grid th{font-size:1.3rem;line-height:1.36;transition:background-color .1s linear;vertical-align:top}.data-grid td._resizing,.data-grid th._resizing{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid td._hidden,.data-grid th._hidden{display:none}.data-grid td._fit,.data-grid th._fit{width:1%}.data-grid td{background-color:#fff;border-left:.1rem dashed #d6d6d6;border-right:.1rem dashed #d6d6d6;color:#303030;padding:1rem}.data-grid td:first-child{border-left-style:solid}.data-grid td:last-child{border-right-style:solid}.data-grid td .action-select-wrap{position:static}.data-grid td .action-select{color:#008bdb;text-decoration:none;background-color:transparent;border:none;font-size:1.3rem;padding:0 3rem 0 0;position:relative}.data-grid td .action-select:hover{color:#0fa7ff;text-decoration:underline}.data-grid td .action-select:hover:after{border-color:#0fa7ff transparent transparent}.data-grid td .action-select:after{border-color:#008bdb transparent transparent;margin:.6rem 0 0 .7rem;right:auto;top:auto}.data-grid td .action-select:before{display:none}.data-grid td .abs-action-menu .action-submenu,.data-grid td .abs-action-menu .action-submenu .action-submenu,.data-grid td .action-menu,.data-grid td .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu,.data-grid td .actions-split .action-menu .action-submenu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu,.data-grid td .actions-split .dropdown-menu .action-submenu .action-submenu{left:auto;min-width:10rem;right:0;text-align:left;top:auto;z-index:1}.data-grid td._update-status-active{background:#bceeff}.data-grid td._update-status-upcoming{background:#ccf391}.data-grid th{background-color:#514943;border:.1rem solid #8a837f;border-left-color:transparent;color:#fff;font-weight:600;padding:0;text-align:left}.data-grid th:first-child{border-left-color:#8a837f}.data-grid th._dragover-left{box-shadow:inset 3px 0 0 0 #fff;z-index:2}.data-grid th._dragover-right{box-shadow:inset -3px 0 0 0 #fff}.data-grid .shadow-div{cursor:col-resize;height:100%;margin-right:-5px;position:absolute;right:0;top:0;width:10px}.data-grid .data-grid-th{background-clip:padding-box;color:#fff;padding:1rem;position:relative;vertical-align:middle}.data-grid .data-grid-th._resize-visible .shadow-div{cursor:auto;display:none}.data-grid .data-grid-th._draggable{cursor:grab}.data-grid .data-grid-th._sortable{cursor:pointer;transition:background-color .1s linear;z-index:1}.data-grid .data-grid-th._sortable:focus,.data-grid .data-grid-th._sortable:hover{background-color:#5f564f}.data-grid .data-grid-th._sortable:active{padding-bottom:.9rem;padding-top:1.1rem}.data-grid .data-grid-th.required>span:after{color:#f38a5e;content:'*';margin-left:.3rem}.data-grid .data-grid-checkbox-cell{overflow:hidden;padding:0;vertical-align:top;width:5.2rem}.data-grid .data-grid-checkbox-cell:hover{cursor:default}.data-grid .data-grid-thumbnail-cell{text-align:center;width:7rem}.data-grid .data-grid-thumbnail-cell img{border:1px solid #d6d6d6;width:5rem}.data-grid .data-grid-multicheck-cell{padding:1rem 1rem .9rem;text-align:center;vertical-align:middle}.data-grid .data-grid-onoff-cell{text-align:center;width:12rem}.data-grid .data-grid-actions-cell{padding-left:2rem;padding-right:2rem;text-align:center;width:1%}.data-grid._hidden{display:none}.data-grid._dragging-copy{box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;opacity:.95;position:fixed;top:0;z-index:1000}.data-grid._dragging-copy .data-grid-th{border:1px solid #007bdb;border-bottom:none}.data-grid._dragging-copy .data-grid-th,.data-grid._dragging-copy .data-grid-th._sortable{cursor:grabbing}.data-grid._dragging-copy tr:last-child td{border-bottom:1px solid #007bdb}.data-grid._dragging-copy td{border-left:1px solid #007bdb;border-right:1px solid #007bdb}.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid._dragging-copy._in-edit .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:rgba(255,251,230,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td,.data-grid._dragging-copy._in-edit .data-grid-editable-row:hover td{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:after,.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{left:0;right:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:before{background-color:rgba(255,255,255,.95)}.data-grid._dragging-copy._in-edit .data-grid-editable-row td:only-child{border-left:1px solid #007bdb;border-right:1px solid #007bdb;left:0}.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-select,.data-grid._dragging-copy._in-edit .data-grid-editable-row .admin__control-text{opacity:.5}.data-grid .data-grid-controls-row td{padding-top:1.6rem}.data-grid .data-grid-controls-row td.data-grid-checkbox-cell{padding-top:.6rem}.data-grid .data-grid-controls-row td [class*=admin__control-],.data-grid .data-grid-controls-row td button{margin-top:-1.7rem}.data-grid._in-edit tr:hover td{background-color:#e6e6e6}.data-grid._in-edit ._odd-row.data-grid-editable-row td,.data-grid._in-edit ._odd-row.data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit ._odd-row td,.data-grid._in-edit ._odd-row:hover td{background-color:#dcdcdc}.data-grid._in-edit .data-grid-editable-row-actions td,.data-grid._in-edit .data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid._in-edit td{background-color:#e6e6e6;pointer-events:none}.data-grid._in-edit .data-grid-checkbox-cell{pointer-events:auto}.data-grid._in-edit .data-grid-editable-row{border:.1rem solid #adadad;border-bottom-color:#c2c2c2}.data-grid._in-edit .data-grid-editable-row:hover td{background-color:#fff}.data-grid._in-edit .data-grid-editable-row td{background-color:#fff;border-bottom-color:#fff;border-left-style:hidden;border-right-style:hidden;border-top-color:#fff;pointer-events:auto;vertical-align:middle}.data-grid._in-edit .data-grid-editable-row td:first-child{border-left-color:#adadad;border-left-style:solid}.data-grid._in-edit .data-grid-editable-row td:first-child:after,.data-grid._in-edit .data-grid-editable-row td:first-child:before{left:0}.data-grid._in-edit .data-grid-editable-row td:last-child{border-right-color:#adadad;border-right-style:solid;left:-.1rem}.data-grid._in-edit .data-grid-editable-row td:last-child:after,.data-grid._in-edit .data-grid-editable-row td:last-child:before{right:0}.data-grid._in-edit .data-grid-editable-row .admin__control-select,.data-grid._in-edit .data-grid-editable-row .admin__control-text{width:100%}.data-grid._in-edit .data-grid-bulk-edit-panel td{vertical-align:bottom}.data-grid .data-grid-editable-row td{border-left-color:#fff;border-left-style:solid;position:relative;z-index:1}.data-grid .data-grid-editable-row td:after{bottom:0;box-shadow:0 5px 5px rgba(0,0,0,.25);content:'';height:.9rem;left:0;margin-top:-1rem;position:absolute;right:0}.data-grid .data-grid-editable-row td:before{background-color:#fff;bottom:0;content:'';height:1rem;left:-10px;position:absolute;right:-10px;z-index:1}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td,.data-grid .data-grid-editable-row.data-grid-editable-row-actions:hover td{background-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:first-child{border-left-color:#fff;border-right-color:#fff}.data-grid .data-grid-editable-row.data-grid-editable-row-actions td:last-child{left:0}.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel td:before,.data-grid .data-grid-editable-row.data-grid-bulk-edit-panel:hover td{background-color:#fffbe6}.data-grid .data-grid-editable-row-actions{left:50%;margin-left:-12.5rem;margin-top:-2px;position:absolute;text-align:center}.data-grid .data-grid-editable-row-actions td{width:25rem}.data-grid .data-grid-editable-row-actions [class*=action-]{min-width:9rem}.data-grid .data-grid-draggable-row-cell{width:1%}.data-grid .data-grid-draggable-row-cell .draggable-handle{padding:0}.data-grid-th._sortable._ascend,.data-grid-th._sortable._descend{padding-right:2.7rem}.data-grid-th._sortable._ascend:before,.data-grid-th._sortable._descend:before{margin-top:-1em;position:absolute;right:1rem;top:50%}.data-grid-th._sortable._ascend:before{content:'\2193'}.data-grid-th._sortable._descend:before{content:'\2191'}.data-grid-checkbox-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:right}.data-grid-checkbox-cell-inner:hover{cursor:pointer}.data-grid-state-cell-inner{display:block;padding:1.1rem 1.8rem .9rem;text-align:center}.data-grid-state-cell-inner>span{display:inline-block;font-style:italic;padding:.6rem 0}.data-grid-row-parent._active>td .data-grid-checkbox-cell-inner:before{content:'\e62b'}.data-grid-row-parent>td .data-grid-checkbox-cell-inner{padding-left:3.7rem;position:relative}.data-grid-row-parent>td .data-grid-checkbox-cell-inner:before{content:'\e628';font-size:1rem;font-weight:700;left:1.35rem;position:absolute;top:1.6rem}.data-grid-th._col-xs{width:1%}.data-grid-info-panel{box-shadow:0 0 5px rgba(0,0,0,.5);margin:2rem .1rem -2rem}.data-grid-info-panel .messages{overflow:hidden}.data-grid-info-panel .messages .message{margin:1rem}.data-grid-info-panel .messages .message:last-child{margin-bottom:1rem}.data-grid-info-panel-actions{padding:1rem;text-align:right}.data-grid-editable-row .admin__field-control{position:relative}.data-grid-editable-row .admin__field-control._error:after{border-color:transparent #ee7d7d transparent transparent;border-style:solid;border-width:0 12px 12px 0;content:'';position:absolute;right:0;top:0}.data-grid-editable-row .admin__field-control._error .admin__control-text{border-color:#ee7d7d}.data-grid-editable-row .admin__field-control._focus:after{display:none}.data-grid-editable-row .admin__field-error{bottom:100%;box-shadow:1px 1px 5px rgba(0,0,0,.5);left:0;margin:0 auto 1.5rem;max-width:32rem;position:absolute;right:0}.data-grid-editable-row .admin__field-error:after,.data-grid-editable-row .admin__field-error:before{border-style:solid;content:'';left:50%;position:absolute;top:100%}.data-grid-editable-row .admin__field-error:after{border-color:#fffbbb transparent transparent;border-width:10px 10px 0;margin-left:-10px;z-index:1}.data-grid-editable-row .admin__field-error:before{border-color:#ee7d7d transparent transparent;border-width:11px 12px 0;margin-left:-12px}.data-grid-bulk-edit-panel .admin__field-label-vertical{display:block;font-size:1.2rem;margin-bottom:.5rem;text-align:left}.data-grid-row-changed{cursor:default;display:block;opacity:.5;position:relative;width:100%;z-index:1}.data-grid-row-changed:after{content:'\e631';display:inline-block}.data-grid-row-changed .data-grid-row-changed-tooltip{background:#f1f1f1;border:1px solid #f1f1f1;border-radius:1px;bottom:100%;box-shadow:0 3px 9px 0 rgba(0,0,0,.3);display:none;font-weight:400;line-height:1.36;margin-bottom:1.5rem;padding:1rem;position:absolute;right:-1rem;text-transform:none;width:27rem;word-break:normal;z-index:2}.data-grid-row-changed._changed{opacity:1;z-index:3}.data-grid-row-changed._changed:hover .data-grid-row-changed-tooltip{display:block}.data-grid-row-changed._changed:hover:before{background:#f1f1f1;border:1px solid #f1f1f1;bottom:100%;box-shadow:4px 4px 3px -1px rgba(0,0,0,.15);content:'';display:block;height:1.6rem;left:50%;margin:0 0 .7rem -.8rem;position:absolute;-ms-transform:rotate(45deg);transform:rotate(45deg);width:1.6rem;z-index:3}.ie9 .data-grid-row-changed._changed:hover:before{display:none}.admin__data-grid-outer-wrap .data-grid-checkbox-cell{overflow:hidden}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner{position:relative}.admin__data-grid-outer-wrap .data-grid-checkbox-cell-inner:before{bottom:0;content:'';height:500%;left:0;position:absolute;right:0;top:0}.admin__data-grid-wrap-static .data-grid-checkbox-cell:hover{cursor:pointer}.admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:1.1rem 1.8rem .9rem;padding:0}.adminhtml-cms-hierarchy-index .admin__data-grid-wrap-static .data-grid-actions-cell:first-child{padding:0}.adminhtml-export-index .admin__data-grid-wrap-static .data-grid-checkbox-cell-inner{margin:0;padding:1.1rem 1.8rem 1.9rem}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before,.admin__control-file-label:before,.admin__control-multiselect,.admin__control-select,.admin__control-text,.admin__control-textarea,.selectmenu{-webkit-appearance:none;background-color:#fff;border:1px solid #adadad;border-radius:1px;box-shadow:none;color:#303030;font-size:1.4rem;font-weight:400;height:auto;line-height:1.36;padding:.6rem 1rem;transition:border-color .1s linear;vertical-align:baseline;width:auto}.admin__control-addon [class*=admin__control-][class]:hover~[class*=admin__addon-]:last-child:before,.admin__control-multiselect:hover,.admin__control-select:hover,.admin__control-text:hover,.admin__control-textarea:hover,.selectmenu:hover,.selectmenu:hover .selectmenu-toggle:before{border-color:#878787}.admin__control-addon [class*=admin__control-][class]:focus~[class*=admin__addon-]:last-child:before,.admin__control-file:active+.admin__control-file-label:before,.admin__control-file:focus+.admin__control-file-label:before,.admin__control-multiselect:focus,.admin__control-select:focus,.admin__control-text:focus,.admin__control-textarea:focus,.selectmenu._focus,.selectmenu._focus .selectmenu-toggle:before{border-color:#007bdb;box-shadow:none;outline:0}.admin__control-addon [class*=admin__control-][class][disabled]~[class*=admin__addon-]:last-child:before,.admin__control-file[disabled]+.admin__control-file-label:before,.admin__control-multiselect[disabled],.admin__control-select[disabled],.admin__control-text[disabled],.admin__control-textarea[disabled]{background-color:#e9e9e9;border-color:#adadad;color:#303030;cursor:not-allowed;opacity:.5}.admin__field-row[class]>.admin__field-control,.admin__fieldset>.admin__field.admin__field-wide[class]>.admin__field-control{clear:left;float:none;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label{display:block;line-height:1.4rem;margin-bottom:.86rem;margin-top:-.14rem;text-align:left;width:auto}.admin__field-row[class]:not(.admin__field-option)>.admin__field-label:before,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)>.admin__field-label:before{display:none}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span{padding-left:1.5rem}.admin__field-row[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__field-row[class]:not(.admin__field-option).required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option)._required>.admin__field-label span:after,.admin__fieldset>.admin__field.admin__field-wide[class]:not(.admin__field-option).required>.admin__field-label span:after{left:0;margin-left:30px}.admin__legend{font-size:1.8rem;font-weight:600;margin-bottom:3rem}.admin__control-checkbox,.admin__control-radio{cursor:pointer;opacity:.01;overflow:hidden;position:absolute;vertical-align:top}.admin__control-checkbox:after,.admin__control-radio:after{display:none}.admin__control-checkbox+label,.admin__control-radio+label{cursor:pointer;display:inline-block}.admin__control-checkbox+label:before,.admin__control-radio+label:before{background-color:#fff;border:1px solid #adadad;color:transparent;float:left;height:1.6rem;text-align:center;vertical-align:top;width:1.6rem}.admin__control-checkbox+.admin__field-label,.admin__control-radio+.admin__field-label{padding-left:2.6rem}.admin__control-checkbox+.admin__field-label:before,.admin__control-radio+.admin__field-label:before{margin:1px 1rem 0 -2.6rem}.admin__control-checkbox:checked+label:before,.admin__control-radio:checked+label:before{color:#514943}.admin__control-checkbox.disabled+label,.admin__control-checkbox[disabled]+label,.admin__control-radio.disabled+label,.admin__control-radio[disabled]+label{color:#303030;cursor:default;opacity:.5}.admin__control-checkbox.disabled+label:before,.admin__control-checkbox[disabled]+label:before,.admin__control-radio.disabled+label:before,.admin__control-radio[disabled]+label:before{background-color:#e9e9e9;border-color:#adadad;cursor:default}._keyfocus .admin__control-checkbox:not(.disabled):focus+label:before,._keyfocus .admin__control-checkbox:not([disabled]):focus+label:before,._keyfocus .admin__control-radio:not(.disabled):focus+label:before,._keyfocus .admin__control-radio:not([disabled]):focus+label:before{border-color:#007bdb}.admin__control-checkbox:not(.disabled):hover+label:before,.admin__control-checkbox:not([disabled]):hover+label:before,.admin__control-radio:not(.disabled):hover+label:before,.admin__control-radio:not([disabled]):hover+label:before{border-color:#878787}.admin__control-radio+label:before{border-radius:1.6rem;content:'';transition:border-color .1s linear,color .1s ease-in}.admin__control-radio.admin__control-radio+label:before{line-height:140%}.admin__control-radio:checked+label{position:relative}.admin__control-radio:checked+label:after{background-color:#514943;border-radius:50%;content:'';height:10px;left:3px;position:absolute;top:4px;width:10px}.admin__control-radio:checked:not(.disabled):hover,.admin__control-radio:checked:not(.disabled):hover+label,.admin__control-radio:checked:not([disabled]):hover,.admin__control-radio:checked:not([disabled]):hover+label{cursor:default}.admin__control-radio:checked:not(.disabled):hover+label:before,.admin__control-radio:checked:not([disabled]):hover+label:before{border-color:#adadad}.admin__control-checkbox+label:before{border-radius:1px;content:'';font-size:0;transition:font-size .1s ease-out,color .1s ease-out,border-color .1s linear}.admin__control-checkbox:checked+label:before{content:'\e62d';font-size:1.1rem;line-height:125%}.admin__control-checkbox:not(:checked)._indeterminate+label:before,.admin__control-checkbox:not(:checked):indeterminate+label:before{color:#514943;content:'-';font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:700}input[type=checkbox].admin__control-checkbox,input[type=radio].admin__control-checkbox{margin:0;position:absolute}.admin__control-text{min-width:4rem}.admin__control-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#adadad,#adadad);background-position:calc(100% - 12px) -34px,100%,calc(100% - 3.2rem) 0;background-size:auto,3.2rem 100%,1px 100%;background-repeat:no-repeat;max-width:100%;min-width:8.5rem;padding-bottom:.5rem;padding-right:4.4rem;padding-top:.5rem;transition:border-color .1s linear}.admin__control-select:hover{border-color:#878787;cursor:pointer}.admin__control-select:focus{background-image:url(../images/arrows-bg.svg),linear-gradient(#e3e3e3,#e3e3e3),linear-gradient(#007bdb,#007bdb);background-position:calc(100% - 12px) 13px,100%,calc(100% - 3.2rem) 0;border-color:#007bdb}.admin__control-select::-ms-expand{display:none}.ie9 .admin__control-select{background-image:none;padding-right:1rem}option:empty{display:none}.admin__control-multiselect{height:auto;max-width:100%;min-width:15rem;overflow:auto;padding:0;resize:both}.admin__control-multiselect optgroup,.admin__control-multiselect option{padding:.5rem 1rem}.admin__control-file-wrapper{display:inline-block;padding:.5rem 1rem;position:relative;z-index:1}.admin__control-file-label:before{content:'';left:0;position:absolute;top:0;width:100%;z-index:0}.admin__control-file{background:0 0;border:0;padding-top:.7rem;position:relative;width:auto;z-index:1}.admin__control-support-text{border:1px solid transparent;display:inline-block;font-size:1.4rem;line-height:1.36;padding-bottom:.6rem;padding-top:.6rem}.admin__control-support-text+[class*=admin__control-],[class*=admin__control-]+.admin__control-support-text{margin-left:.7rem}.admin__control-service{float:left;margin:.8rem 0 0 3rem}.admin__control-textarea{height:8.48rem;line-height:1.18;padding-top:.8rem;resize:vertical}.admin__control-addon{-ms-flex-direction:row;flex-direction:row;display:inline-flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;position:relative;width:100%;z-index:1}.admin__control-addon>[class*=admin__addon-],.admin__control-addon>[class*=admin__control-]{-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-flex-grow:0;-ms-flex-positive:0;flex-grow:0;-ms-flex-negative:0;flex-shrink:0;position:relative;z-index:1}.admin__control-addon .admin__control-select{width:auto}.admin__control-addon .admin__control-text{margin:.1rem;padding:.5rem .9rem;width:100%}.admin__control-addon [class*=admin__control-][class]{appearence:none;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-order:1;order:1;-ms-flex-negative:1;flex-shrink:1;background-color:transparent;border-color:transparent;box-shadow:none;vertical-align:top}.admin__control-addon [class*=admin__control-][class]+[class*=admin__control-]{border-left-color:#adadad}.admin__control-addon [class*=admin__control-][class] :focus{box-shadow:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child{padding-left:1rem;position:static!important;z-index:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child>*{position:relative;vertical-align:top;z-index:1}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:empty{padding:0}.admin__control-addon [class*=admin__control-][class]~[class*=admin__addon-]:last-child:before{bottom:0;box-sizing:border-box;content:'';left:0;position:absolute;top:0;width:100%;z-index:-1}.admin__addon-prefix,.admin__addon-suffix{border:0;box-sizing:border-box;color:#858585;display:inline-block;font-size:1.4rem;font-weight:400;height:3.2rem;line-height:3.2rem;padding:0}.admin__addon-suffix{-ms-flex-order:3;order:3}.admin__addon-suffix:last-child{padding-right:1rem}.admin__addon-prefix{-ms-flex-order:0;order:0}.ie9 .admin__control-addon:after{clear:both;content:'';display:block;height:0;overflow:hidden}.ie9 .admin__addon{min-width:0;overflow:hidden;text-align:right;white-space:nowrap;width:auto}.ie9 .admin__addon [class*=admin__control-]{display:inline}.ie9 .admin__addon-prefix{float:left}.ie9 .admin__addon-suffix{float:right}.admin__control-collapsible{width:100%}.admin__control-collapsible ._dragged .admin__collapsible-block-wrapper .admin__collapsible-title{background:#d0d0d0}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before,.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{background:#008bdb;content:'';display:block;height:3px;left:0;position:absolute;right:0}.admin__control-collapsible ._dragover-top .admin__collapsible-block-wrapper:before{top:-3px}.admin__control-collapsible ._dragover-bottom .admin__collapsible-block-wrapper:before{bottom:-3px}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper{border:0;margin:0;position:relative}.admin__control-collapsible .admin__collapsible-block-wrapper.fieldset-wrapper .fieldset-wrapper-title{background:#f8f8f8;border:2px solid #ccc}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title{font-size:1.4rem;font-weight:400;line-height:1;padding:1.6rem 4rem 1.6rem 3.8rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .admin__collapsible-title:before{left:1rem;right:auto;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding:0;position:absolute;right:1rem;top:1.4rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete:before{content:'\e630';font-size:2rem}.admin__control-collapsible .admin__collapsible-block-wrapper .fieldset-wrapper-title .action-delete>span{display:none}.admin__control-collapsible .admin__collapsible-content{background-color:#fff;margin-bottom:1rem}.admin__control-collapsible .admin__collapsible-content>.fieldset-wrapper{border:1px solid #ccc;margin-top:-1px;padding:1rem}.admin__control-collapsible .admin__collapsible-content .admin__fieldset{padding:0}.admin__control-collapsible .admin__collapsible-content .admin__field:last-child{margin-bottom:0}.admin__control-table-wrapper{max-width:100%;overflow-x:auto;overflow-y:hidden}.admin__control-table{width:100%}.admin__control-table thead{background-color:transparent}.admin__control-table tbody td{vertical-align:top}.admin__control-table tfoot th{padding-bottom:1.3rem}.admin__control-table tfoot th.validation{padding-bottom:0;padding-top:0}.admin__control-table tfoot td{border-top:1px solid #fff}.admin__control-table tfoot .admin__control-table-pagination{float:right;padding-bottom:0}.admin__control-table tfoot .action-previous{margin-right:.5rem}.admin__control-table tfoot .action-next{margin-left:.9rem}.admin__control-table tr:last-child td{border-bottom:none}.admin__control-table tr._dragover-top td{box-shadow:inset 0 3px 0 0 #008bdb}.admin__control-table tr._dragover-bottom td{box-shadow:inset 0 -3px 0 0 #008bdb}.admin__control-table tr._dragged td,.admin__control-table tr._dragged th{background:#d0d0d0}.admin__control-table td,.admin__control-table th{background-color:#efefef;border:0;border-bottom:1px solid #fff;padding:1.3rem 1rem 1.3rem 0;text-align:left;vertical-align:top}.admin__control-table td:first-child,.admin__control-table th:first-child{padding-left:1rem}.admin__control-table td>.admin__control-select,.admin__control-table td>.admin__control-text,.admin__control-table th>.admin__control-select,.admin__control-table th>.admin__control-text{width:100%}.admin__control-table td._hidden,.admin__control-table th._hidden{display:none}.admin__control-table td._fit,.admin__control-table th._fit{width:1px}.admin__control-table th{color:#303030;font-size:1.4rem;font-weight:600;vertical-align:bottom}.admin__control-table th._required span:after{color:#eb5202;content:'*'}.admin__control-table .control-table-actions-th{white-space:nowrap}.admin__control-table .control-table-actions-cell{padding-top:1.8rem;text-align:center;width:1%}.admin__control-table .control-table-options-th{text-align:center;width:10rem}.admin__control-table .control-table-options-cell{text-align:center}.admin__control-table .control-table-text{line-height:3.2rem}.admin__control-table .col-draggable{padding-top:2.2rem;width:1%}.admin__control-table .action-delete{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.admin__control-table .action-delete:hover{background-color:transparent;border-color:transparent;box-shadow:none}.admin__control-table .action-delete:before{content:'\e630';font-size:2rem}.admin__control-table .action-delete>span{display:none}.admin__control-table .draggable-handle{padding:0}.admin__control-table._dragged{outline:#007bdb solid 1px}.admin__control-table-action{background-color:#efefef;border-top:1px solid #fff;padding:1.3rem 1rem}.admin__dynamic-rows._dragged{opacity:.95;position:absolute;z-index:999}.admin__dynamic-rows.admin__control-table .admin__control-fields>.admin__field{border:0;padding:0}.admin__dynamic-rows td>.admin__field{border:0;margin:0;padding:0}.admin__control-table-pagination{padding-bottom:1rem}.admin__control-table-pagination .admin__data-grid-pager{float:right}.admin__field-tooltip{display:inline-block;margin-top:.5rem;max-width:45px;overflow:visible;vertical-align:top;width:0}.admin__field-tooltip:hover{position:relative;z-index:500}.admin__field-option .admin__field-tooltip{margin-top:.5rem}.admin__field-tooltip .admin__field-tooltip-action{margin-left:2rem;position:relative;z-index:2;display:inline-block;text-decoration:none}.admin__field-tooltip .admin__field-tooltip-action:before{-webkit-font-smoothing:antialiased;font-size:2.2rem;line-height:1;color:#514943;content:'\e633';font-family:Icons;vertical-align:middle;display:inline-block;font-weight:400;overflow:hidden;speak:none;text-align:center}.admin__field-tooltip .admin__control-text:focus+.admin__field-tooltip-content,.admin__field-tooltip:hover .admin__field-tooltip-content{display:block}.admin__field-tooltip .admin__field-tooltip-content{bottom:3.8rem;display:none;right:-2.3rem}.admin__field-tooltip .admin__field-tooltip-content:after,.admin__field-tooltip .admin__field-tooltip-content:before{border:1.6rem solid transparent;height:0;width:0;border-top-color:#afadac;content:'';display:block;position:absolute;right:2rem;top:100%;z-index:3}.admin__field-tooltip .admin__field-tooltip-content:after{border-top-color:#fffbbb;margin-top:-1px;z-index:4}.abs-admin__field-tooltip-content,.admin__field-tooltip .admin__field-tooltip-content{box-shadow:0 2px 8px 0 rgba(0,0,0,.3);background:#fffbbb;border:1px solid #afadac;border-radius:1px;padding:1.5rem 2.5rem;position:absolute;width:32rem;z-index:1}.admin__field-fallback-reset{font-size:1.25rem;white-space:nowrap;width:30px}.admin__field-fallback-reset>span{margin-left:.5rem;position:relative}.admin__field-fallback-reset:active{-ms-transform:scale(0.98);transform:scale(0.98)}.admin__field-fallback-reset:before{transition:color .1s linear;content:'\e642';font-size:1.3rem;margin-left:.5rem}.admin__field-fallback-reset:hover{cursor:pointer;text-decoration:none}.admin__field-fallback-reset:focus{background:0 0}.abs-field-size-x-small,.abs-field-sizes.admin__field-x-small>.admin__field-control,.admin__field.admin__field-x-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-x-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-x-small>.admin__field-control{width:8rem}.abs-field-size-small,.abs-field-sizes.admin__field-small>.admin__field-control,.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control,.admin__field.admin__field-small>.admin__field-control,.admin__fieldset>.admin__field.admin__field-small>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-small>.admin__field-control{width:15rem}.abs-field-size-medium,.abs-field-sizes.admin__field-medium>.admin__field-control,.admin__field.admin__field-medium>.admin__field-control,.admin__fieldset>.admin__field.admin__field-medium>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-medium>.admin__field-control{width:34rem}.abs-field-size-large,.abs-field-sizes.admin__field-large>.admin__field-control,.admin__field.admin__field-large>.admin__field-control,.admin__fieldset>.admin__field.admin__field-large>.admin__field-control,[class*=admin__control-grouped]>.admin__field.admin__field-large>.admin__field-control{width:64rem}.abs-field-no-label,.admin__field-group-additional,.admin__field-no-label,.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-control{margin-left:calc((100%) * .25 + 30px)}.admin__fieldset{border:0;margin:0;min-width:0;padding:0}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title{padding-left:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section>.fieldset-wrapper-title strong{font-size:1.7rem;font-weight:600}.admin__fieldset .fieldset-wrapper.admin__fieldset-section .admin__fieldset-wrapper-content>.admin__fieldset{padding-top:1rem}.admin__fieldset .fieldset-wrapper.admin__fieldset-section:last-child .admin__fieldset-wrapper-content>.admin__fieldset{padding-bottom:0}.admin__fieldset>.admin__field{border:0;margin:0 0 0 -30px;padding:0}.admin__fieldset>.admin__field:after{clear:both;content:'';display:table}.admin__fieldset>.admin__field>.admin__field-control{width:calc((100%) * .5 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.admin__fieldset>.admin__field.admin__field-no-label>.admin__field-label{display:none}.admin__fieldset>.admin__field+.admin__field._empty._no-header{margin-top:-3rem}.admin__fieldset-product-websites{position:relative;z-index:300}.admin__fieldset-note{margin-bottom:2rem}.admin__form-field{border:0;margin:0;padding:0}.admin__field-control .admin__control-text,.admin__field-control .admin__control-textarea,.admin__form-field-control .admin__control-text,.admin__form-field-control .admin__control-textarea{width:100%}.admin__field-label{color:#303030;cursor:pointer;margin:0;text-align:right}.admin__field-label+br{display:none}.admin__field:not(.admin__field-option)>.admin__field-label{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.4rem;font-weight:600;line-height:3.2rem;padding:0;white-space:nowrap}.admin__field:not(.admin__field-option)>.admin__field-label:before{opacity:0;visibility:hidden;content:'.';margin-left:-7px;overflow:hidden}.admin__field:not(.admin__field-option)>.admin__field-label span{display:inline-block;line-height:1.2;vertical-align:middle;white-space:normal}.admin__field:not(.admin__field-option)>.admin__field-label span[data-config-scope]{position:relative}._required>.admin__field-label>span:after,.required>.admin__field-label>span:after{color:#eb5202;content:'*';display:inline-block;font-size:1.6rem;font-weight:500;line-height:1;margin-left:10px;margin-top:.2rem;position:absolute;z-index:1}._disabled>.admin__field-label{color:#999;cursor:default}.admin__field{margin-bottom:0}.admin__field+.admin__field{margin-top:1.5rem}.admin__field:not(.admin__field-option)~.admin__field-option{margin-top:.5rem}.admin__field.admin__field-option~.admin__field-option{margin-top:.9rem}.admin__field~.admin__field-option:last-child{margin-bottom:.8rem}.admin__fieldset>.admin__field{margin-bottom:3rem;position:relative}.admin__field legend.admin__field-label{opacity:0}.admin__field[data-config-scope]:before{color:gray;content:attr(data-config-scope);display:inline-block;font-size:1.2rem;left:calc((100%) * .75 - 30px);line-height:3.2rem;margin-left:60px;position:absolute;width:calc((100%) * .25 - 30px)}.admin__field-control .admin__field[data-config-scope]:nth-child(n+2):before{content:''}.admin__field._error .admin__field-control [class*=admin__addon-]:before,.admin__field._error .admin__field-control [class*=admin__control-] [class*=admin__addon-]:before,.admin__field._error .admin__field-control>[class*=admin__control-]{border-color:#e22626}.admin__field._disabled,.admin__field._disabled:hover{box-shadow:inherit;cursor:inherit;opacity:1;outline:inherit}.admin__field._hidden{display:none}.admin__field-control+.admin__field-control{margin-top:1.5rem}.admin__field-control._with-tooltip>.admin__control-addon,.admin__field-control._with-tooltip>.admin__control-select,.admin__field-control._with-tooltip>.admin__control-text,.admin__field-control._with-tooltip>.admin__control-textarea,.admin__field-control._with-tooltip>.admin__field-option{max-width:calc(100% - 45px - 4px)}.admin__field-control._with-tooltip .admin__field-tooltip{width:auto}.admin__field-control._with-tooltip .admin__field-option{display:inline-block}.admin__field-control._with-reset>.admin__control-addon,.admin__field-control._with-reset>.admin__control-text,.admin__field-control._with-reset>.admin__control-textarea{width:calc(100% - 30px - .5rem - 4px)}.admin__field-control._with-reset .admin__field-fallback-reset{margin-left:.5rem;margin-top:1rem;vertical-align:top}.admin__field-control._with-reset._with-tooltip>.admin__control-addon,.admin__field-control._with-reset._with-tooltip>.admin__control-text,.admin__field-control._with-reset._with-tooltip>.admin__control-textarea{width:calc(100% - 30px - .5rem - 45px - 8px)}.admin__fieldset>.admin__field-collapsible{margin-bottom:0}.admin__fieldset>.admin__field-collapsible .admin__field-control{border-top:1px solid #ccc;display:block;font-size:1.7rem;font-weight:700;padding:1.7rem 0;width:calc(97%)}.admin__fieldset>.admin__field-collapsible .admin__field-option{padding-top:0}.admin__field-collapsible+div{margin-top:2.5rem}.admin__field-collapsible .admin__control-radio+label:before{height:1.8rem;width:1.8rem}.admin__field-collapsible .admin__control-radio:checked+label:after{left:4px;top:5px}.admin__field-error{background:#fffbbb;border:1px solid #ee7d7d;box-sizing:border-box;color:#555;display:block;font-size:1.2rem;font-weight:400;line-height:1.2;margin:.2rem 0 0;padding:.8rem 1rem .9rem}.admin__field-note{color:#303030;font-size:1.2rem;margin:10px 0 0;padding:0}.admin__additional-info{padding-top:1rem}.admin__field-option{padding-top:.7rem}.admin__field-option .admin__field-label{text-align:left}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2),.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1){display:inline-block}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option{display:inline-block;margin-left:41px;margin-top:0}.admin__field-control>.admin__field-option:nth-child(1):nth-last-child(2)+.admin__field-option:before,.admin__field-control>.admin__field-option:nth-child(2):nth-last-child(1)+.admin__field-option:before{background:#cacaca;content:'';display:inline-block;height:20px;margin-left:-20px;position:absolute;width:1px}.admin__field-value{display:inline-block;padding-top:.7rem}.admin__field-service{padding-top:1rem}.admin__control-fields>.admin__field:first-child,[class*=admin__control-grouped]>.admin__field:first-child{position:static}.admin__control-fields>.admin__field:first-child>.admin__field-label,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label{width:calc((100%) * .25 - 30px);float:left;margin-left:30px;background:#fff;cursor:pointer;left:0;position:absolute;top:0}.admin__control-fields>.admin__field:first-child>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field:first-child>.admin__field-label span:before{display:block}.admin__control-fields>.admin__field._disabled>.admin__field-label,[class*=admin__control-grouped]>.admin__field._disabled>.admin__field-label{cursor:default}.admin__control-fields>.admin__field>.admin__field-label span:before,[class*=admin__control-grouped]>.admin__field>.admin__field-label span:before{display:none}.admin__control-fields .admin__field-label~.admin__field-control{width:100%}.admin__control-fields .admin__field-option{padding-top:0}[class*=admin__control-grouped]{box-sizing:border-box;display:table;width:100%}[class*=admin__control-grouped]>.admin__field{display:table-cell;vertical-align:top}[class*=admin__control-grouped]>.admin__field>.admin__field-control{float:none;width:100%}[class*=admin__control-grouped]>.admin__field.admin__field-default,[class*=admin__control-grouped]>.admin__field.admin__field-large,[class*=admin__control-grouped]>.admin__field.admin__field-medium,[class*=admin__control-grouped]>.admin__field.admin__field-small,[class*=admin__control-grouped]>.admin__field.admin__field-x-small{width:1px}[class*=admin__control-grouped]>.admin__field.admin__field-default+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-large+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-medium+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-small+.admin__field:last-child,[class*=admin__control-grouped]>.admin__field.admin__field-x-small+.admin__field:last-child{width:auto}[class*=admin__control-grouped]>.admin__field:nth-child(n+2){padding-left:20px}.admin__control-group-equal{table-layout:fixed}.admin__control-group-equal>.admin__field{width:50%}.admin__field-control-group{margin-top:.8rem}.admin__field-control-group>.admin__field{padding:0}.admin__control-grouped-date>.admin__field-date{white-space:nowrap;width:1px}.admin__control-grouped-date>.admin__field-date.admin__field>.admin__field-control{float:left;position:relative}.admin__control-grouped-date>.admin__field-date+.admin__field:last-child{width:auto}.admin__control-grouped-date>.admin__field-date+.admin__field-date>.admin__field-label{float:left;padding-right:20px}.admin__control-grouped-date .ui-datepicker-trigger{left:100%;top:0}.admin__field-group-columns.admin__field-control.admin__control-grouped{width:calc((100%) * 1 - 30px);float:left;margin-left:30px}.admin__field-group-columns>.admin__field:first-child>.admin__field-label{float:none;margin:0;opacity:1;position:static;text-align:left}.admin__field-group-columns .admin__control-select{width:100%}.admin__field-group-additional{clear:both}.admin__field-group-additional .action-advanced{margin-top:1rem}.admin__field-group-additional .action-secondary{width:100%}.admin__field-group-show-label{white-space:nowrap}.admin__field-group-show-label>.admin__field-control,.admin__field-group-show-label>.admin__field-label{display:inline-block;vertical-align:top}.admin__field-group-show-label>.admin__field-label{margin-right:20px}.admin__field-complex{margin:1rem 0 3rem;padding-left:1rem}.admin__field:not(._hidden)+.admin__field-complex{margin-top:3rem}.admin__field-complex .admin__field-complex-title{clear:both;color:#303030;font-size:1.7rem;font-weight:600;letter-spacing:.025em;margin-bottom:1rem}.admin__field-complex .admin__field-complex-elements{float:right;max-width:40%}.admin__field-complex .admin__field-complex-elements button{margin-left:1rem}.admin__field-complex .admin__field-complex-content{max-width:60%;overflow:hidden}.admin__field-complex .admin__field-complex-text{margin-left:-1rem}.admin__field-complex+.admin__field._empty._no-header{margin-top:-3rem}.admin__legend{float:left;position:static;width:100%}.admin__legend+br{clear:left;display:block;height:0;overflow:hidden}.message{margin-bottom:3rem}.message-icon-top:before{margin-top:0;top:1.8rem}.nav{background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;border-top:1px solid #e3e3e3;display:none;margin-bottom:3rem;padding:2.2rem 1.5rem 0 0}.nav .btn-group,.nav-bar-outer-actions{float:right;margin-bottom:1.7rem}.nav .btn-group .btn-wrap,.nav-bar-outer-actions .btn-wrap{float:right;margin-left:.5rem;margin-right:.5rem}.nav .btn-group .btn-wrap .btn,.nav-bar-outer-actions .btn-wrap .btn{padding-left:.5rem;padding-right:.5rem}.nav-bar-outer-actions{margin-top:-10.6rem;padding-right:1.5rem}.btn-wrap-try-again{width:9.5rem}.btn-wrap-next,.btn-wrap-prev{width:8.5rem}.nav-bar{counter-reset:i;float:left;margin:0 1rem 1.7rem 0;padding:0;position:relative;white-space:nowrap}.nav-bar:before{background-color:#d4d4d4;background-repeat:repeat-x;background-image:linear-gradient(to bottom,#d1d1d1 0,#d4d4d4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#d1d1d1', endColorstr='#d4d4d4', GradientType=0);border-bottom:1px solid #d9d9d9;border-top:1px solid #bfbfbf;content:'';height:1rem;left:5.15rem;position:absolute;right:5.15rem;top:.7rem}.nav-bar>li{display:inline-block;font-size:0;position:relative;vertical-align:top;width:10.3rem}.nav-bar>li:first-child:after{display:none}.nav-bar>li:after{background-color:#514943;content:'';height:.5rem;left:calc(-50% + .25rem);position:absolute;right:calc(50% + .7rem);top:.9rem}.nav-bar>li.disabled:before,.nav-bar>li.ui-state-disabled:before{bottom:0;content:'';left:0;position:absolute;right:0;top:0;z-index:1}.nav-bar>li.active~li:after,.nav-bar>li.ui-state-active~li:after{display:none}.nav-bar>li.active~li a:after,.nav-bar>li.ui-state-active~li a:after{background-color:transparent;border-color:transparent;color:#a6a6a6}.nav-bar>li.active a,.nav-bar>li.ui-state-active a{color:#000}.nav-bar>li.active a:hover,.nav-bar>li.ui-state-active a:hover{cursor:default}.nav-bar>li.active a:after,.nav-bar>li.ui-state-active a:after{background-color:#fff;content:''}.nav-bar a{color:#514943;display:block;font-size:1.2rem;font-weight:600;line-height:1.2;overflow:hidden;padding:3rem .5em 0;position:relative;text-align:center;text-overflow:ellipsis}.nav-bar a:hover{text-decoration:none}.nav-bar a:after{background-color:#514943;border:.4rem solid #514943;border-radius:100%;color:#fff;content:counter(i);counter-increment:i;height:1.5rem;left:50%;line-height:.6;margin-left:-.8rem;position:absolute;right:auto;text-align:center;top:.4rem;width:1.5rem}.nav-bar a:before{background-color:#d6d6d6;border:1px solid transparent;border-bottom-color:#d9d9d9;border-radius:100%;border-top-color:#bfbfbf;content:'';height:2.3rem;left:50%;line-height:1;margin-left:-1.2rem;position:absolute;top:0;width:2.3rem}.tooltip{display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.19rem;font-weight:400;line-height:1.4;opacity:0;position:absolute;visibility:visible;z-index:10}.tooltip.in{opacity:.9}.tooltip.top{margin-top:-4px;padding:8px 0}.tooltip.right{margin-left:4px;padding:0 8px}.tooltip.bottom{margin-top:4px;padding:8px 0}.tooltip.left{margin-left:-4px;padding:0 8px}.tooltip p:last-child{margin-bottom:0}.tooltip-inner{background-color:#fff;border:1px solid #adadad;border-radius:0;box-shadow:1px 1px 1px #ccc;color:#41362f;max-width:31rem;padding:.5em 1em;text-decoration:none}.tooltip-arrow,.tooltip-arrow:after{border:solid transparent;height:0;position:absolute;width:0}.tooltip-arrow:after{content:'';position:absolute}.tooltip.top .tooltip-arrow,.tooltip.top .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:50%;margin-left:-8px}.tooltip.top-left .tooltip-arrow,.tooltip.top-left .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;margin-bottom:-8px;right:8px}.tooltip.top-right .tooltip-arrow,.tooltip.top-right .tooltip-arrow:after{border-top-color:#949494;border-width:8px 8px 0;bottom:0;left:8px;margin-bottom:-8px}.tooltip.right .tooltip-arrow,.tooltip.right .tooltip-arrow:after{border-right-color:#949494;border-width:8px 8px 8px 0;left:1px;margin-top:-8px;top:50%}.tooltip.right .tooltip-arrow:after{border-right-color:#fff;border-width:6px 7px 6px 0;margin-left:0;margin-top:-6px}.tooltip.left .tooltip-arrow,.tooltip.left .tooltip-arrow:after{border-left-color:#949494;border-width:8px 0 8px 8px;margin-top:-8px;right:0;top:50%}.tooltip.bottom .tooltip-arrow,.tooltip.bottom .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:50%;margin-left:-8px;top:0}.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;margin-top:-8px;right:8px;top:0}.tooltip.bottom-right .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow:after{border-bottom-color:#949494;border-width:0 8px 8px;left:8px;margin-top:-8px;top:0}.password-strength{display:block;margin:0 -.3rem 1em;white-space:nowrap}.password-strength.password-strength-too-short .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child,.password-strength.password-strength-weak .password-strength-item:first-child+.password-strength-item{background-color:#e22626}.password-strength.password-strength-fair .password-strength-item:first-child,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-fair .password-strength-item:first-child+.password-strength-item+.password-strength-item{background-color:#ef672f}.password-strength.password-strength-good .password-strength-item:first-child,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item,.password-strength.password-strength-good .password-strength-item:first-child+.password-strength-item+.password-strength-item+.password-strength-item,.password-strength.password-strength-strong .password-strength-item{background-color:#79a22e}.password-strength .password-strength-item{background-color:#ccc;display:inline-block;font-size:0;height:1.4rem;margin-right:.3rem;width:calc(20% - .6rem)}@keyframes progress-bar-stripes{from{background-position:4rem 0}to{background-position:0 0}}.progress{background-color:#fafafa;border:1px solid #ccc;clear:left;height:3rem;margin-bottom:3rem;overflow:hidden}.progress-bar{background-color:#79a22e;color:#fff;float:left;font-size:1.19rem;height:100%;line-height:3rem;text-align:center;transition:width .6s ease;width:0}.progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.progress-bar-text-description{margin-bottom:1.6rem}.progress-bar-text-progress{text-align:right}.page-columns .page-inner-sidebar{margin:0 0 3rem}.page-header{margin-bottom:2.7rem;padding-bottom:2rem;position:relative}.page-header:before{border-bottom:1px solid #e3e3e3;bottom:0;content:'';display:block;height:1px;left:3rem;position:absolute;right:3rem}.container .page-header:before{content:normal}.page-header .message{margin-bottom:1.8rem}.page-header .message+.message{margin-top:-1.5rem}.page-header .admin__action-dropdown,.page-header .search-global-input{transition:none}.container .page-header{margin-bottom:0}.page-title-wrapper{margin-top:1.1rem}.container .page-title-wrapper{background:url(../../pub/images/logo.svg) no-repeat;min-height:41px;padding:4px 0 0 45px}.admin__menu .level-0:first-child>a{margin-top:1.6rem}.admin__menu .level-0:first-child>a:after{top:-1.6rem}.admin__menu .level-0:first-child._active>a:after{display:block}.admin__menu .level-0>a{padding-bottom:1.3rem;padding-top:1.3rem}.admin__menu .level-0>a:before{margin-bottom:.7rem}.admin__menu .item-home>a:before{content:'\e611';font-size:2.3rem;padding-top:-.1rem}.admin__menu .item-component>a:before{content:'\e612'}.admin__menu .item-extension>a:before{content:'\e612'}.admin__menu .item-module>a:before{content:'\e647'}.admin__menu .item-upgrade>a:before{content:'\e614'}.admin__menu .item-system-config>a:before{content:'\e610'}.admin__menu .item-tools>a:before{content:'\e613'}.modal-sub-title{font-size:1.7rem;font-weight:600}.modal-connect-signin .modal-inner-wrap{max-width:80rem}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{-webkit-overflow-scrolling:touch;bottom:0;box-sizing:border-box;left:0;overflow:auto;position:fixed;right:0;top:0;z-index:999}.ngdialog *,.ngdialog:after,.ngdialog:before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation *{animation:none!important}.ngdialog.ngdialog-closing .ngdialog-content,.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-animation:ngdialog-fadeout .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadeout .5s}.ngdialog-overlay{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s;background:rgba(0,0,0,.4);bottom:0;left:0;position:fixed;right:0;top:0}.ngdialog-content{-webkit-animation:ngdialog-fadein .5s;-webkit-backface-visibility:hidden;animation:ngdialog-fadein .5s}body.ngdialog-open{overflow:hidden}.component-indicator{border-radius:50%;cursor:help;display:inline-block;height:16px;text-align:center;vertical-align:middle;width:16px}.component-indicator::after,.component-indicator::before{background:#fff;display:block;opacity:0;position:absolute;transition:opacity .2s linear .1s;visibility:hidden}.component-indicator::before{border:1px solid #adadad;border-radius:1px;box-shadow:0 0 2px rgba(0,0,0,.4);content:attr(data-label);font-size:1.2rem;margin:30px 0 0 -10px;min-width:50px;padding:4px 5px}.component-indicator::after{border-color:#999;border-style:solid;border-width:1px 0 0 1px;box-shadow:-1px -1px 1px rgba(0,0,0,.1);content:'';height:10px;margin:9px 0 0 5px;-ms-transform:rotate(45deg);transform:rotate(45deg);width:10px}.component-indicator:hover::after,.component-indicator:hover::before{opacity:1;transition:opacity .2s linear;visibility:visible}.component-indicator span{display:block;height:16px;overflow:hidden;width:16px}.component-indicator span:before{content:'';display:block;font-family:Icons;font-size:16px;height:100%;line-height:16px;width:100%}.component-indicator._on{background:#79a22e}.component-indicator._off{background:#e22626}.component-indicator._off span:before{background:#fff;height:4px;margin:8px auto 20px;width:12px}.component-indicator._info{background:0 0}.component-indicator._info span{width:21px}.component-indicator._info span:before{color:#008bdb;content:'\e648';font-family:Icons;font-size:16px}.component-indicator._tooltip{background:0 0;margin:0 0 8px 5px}.component-indicator._tooltip a{width:21px}.component-indicator._tooltip a:hover{text-decoration:none}.component-indicator._tooltip a:before{color:#514943;content:'\e633';font-family:Icons;font-size:16px}.col-manager-item-name .data-grid-data{padding-left:5px}.col-manager-item-name .ng-hide+.data-grid-data{padding-left:24px}.col-manager-item-name ._hide-dependencies,.col-manager-item-name ._show-dependencies{cursor:pointer;padding-left:24px;position:relative}.col-manager-item-name ._hide-dependencies:before,.col-manager-item-name ._show-dependencies:before{display:block;font-family:Icons;font-size:12px;left:0;position:absolute;top:1px}.col-manager-item-name ._show-dependencies:before{content:'\e62b'}.col-manager-item-name ._hide-dependencies:before{content:'\e628'}.col-manager-item-name ._no-dependencies{padding-left:24px}.product-modules-block{font-size:1.2rem;padding:15px 0 0}.col-manager-item-name .product-modules-block{padding-left:1rem}.product-modules-descriprion,.product-modules-title{font-weight:700;margin:0 0 7px}.product-modules-list{font-size:1.1rem;list-style:none;margin:0}.col-manager-item-name .product-modules-list{margin-left:15px}.col-manager-item-name .product-modules-list li{padding:0 0 0 15px;position:relative}.product-modules-list li{margin:0 0 .5rem}.product-modules-list .component-indicator{height:10px;left:0;position:absolute;top:3px;width:10px}.module-summary{white-space:nowrap}.module-summary-title{font-size:2.1rem;margin-right:1rem}.app-updater .nav{display:block;margin-bottom:3.1rem;margin-top:-2.8rem}.app-updater .nav-bar-outer-actions{margin-top:1rem;padding-right:0}.app-updater .nav-bar-outer-actions .btn-wrap-cancel{margin-right:2.6rem}.main{padding-bottom:2rem;padding-top:3rem}.header{display:none}.header .logo{float:left;height:4.1rem;width:3.5rem}.header-title{font-size:2.8rem;letter-spacing:.02em;line-height:1.4;margin:2.5rem 0 3.5rem 5rem}.page-title{margin-bottom:1rem}.page-sub-title{font-size:2rem}.accent-box{margin-bottom:2rem}.accent-box .btn-prime{margin-top:1.5rem}.spinner.side{float:left;font-size:2.4rem;margin-left:2rem;margin-top:-5px}.page-landing{margin:7.6% auto 0;max-width:44rem;text-align:center}.page-landing .logo{height:5.6rem;margin-bottom:2rem;width:19.2rem}.page-landing .text-version{margin-bottom:3rem}.page-landing .text-welcome{margin-bottom:6.5rem}.page-landing .text-terms{margin-bottom:2.5rem;text-align:center}.page-landing .btn-submit,.page-license .license-text{margin-bottom:2rem}.page-license .page-license-footer{text-align:right}.readiness-check-item{margin-bottom:4rem;min-height:2.5rem}.readiness-check-item .spinner{float:left;font-size:2.5rem;margin:-.4rem 0 0 1.7rem}.readiness-check-title{font-size:1.4rem;font-weight:700;margin-bottom:.1rem;margin-left:5.7rem}.readiness-check-content{margin-left:5.7rem;margin-right:22rem;position:relative}.readiness-check-content .readiness-check-title{margin-left:0}.readiness-check-content .list{margin-top:-.3rem}.readiness-check-side{left:100%;padding-left:2.4rem;position:absolute;top:0;width:22rem}.readiness-check-side .side-title{margin-bottom:0}.readiness-check-icon{float:left;margin-left:1.7rem;margin-top:.3rem}.extensions-information{margin-bottom:5rem}.extensions-information h3{font-size:1.4rem;margin-bottom:1.3rem}.extensions-information .message{margin-bottom:2.5rem}.extensions-information .message:before{margin-top:0;top:1.8rem}.extensions-information .extensions-container{padding:0 2rem}.extensions-information .list{margin-bottom:1rem}.extensions-information .list select{cursor:pointer}.extensions-information .list select:disabled{background:#ccc;cursor:default}.extensions-information .list .extension-delete{font-size:1.7rem;padding-top:0}.delete-modal-wrap{padding:0 4% 4rem}.delete-modal-wrap h3{font-size:3.4rem;display:inline-block;font-weight:300;margin:0 0 2rem;padding:.9rem 0 0;vertical-align:top}.delete-modal-wrap .actions{padding:3rem 0 0}.page-web-configuration .form-el-insider-wrap{width:auto}.page-web-configuration .form-el-insider{width:15.4rem}.page-web-configuration .form-el-insider-input .form-el-input{width:16.5rem}.customize-your-store .advanced-modules-count,.customize-your-store .advanced-modules-select{padding-left:1.5rem}.customize-your-store .customize-your-store-advanced{min-width:0}.customize-your-store .message-error:before{margin-top:0;top:1.8rem}.customize-your-store .message-error a{color:#333;text-decoration:underline}.customize-your-store .message-error .form-label:before{background:#fff}.customize-your-store .customize-database-clean p{margin-top:2.5rem}.content-install{margin-bottom:2rem}.console{border:1px solid #ccc;font-family:'Courier New',Courier,monospace;font-weight:300;height:20rem;margin:1rem 0 2rem;overflow-y:auto;padding:1.5rem 2rem 2rem;resize:vertical}.console .text-danger{color:#e22626}.console .text-success{color:#090}.console .hidden{display:none}.content-success .btn-prime{margin-top:1.5rem}.jumbo-title{font-size:3.6rem}.jumbo-title .jumbo-icon{font-size:3.8rem;margin-right:.25em;position:relative;top:.15em}.install-database-clean{margin-top:4rem}.install-database-clean .btn{margin-right:1rem}.page-sub-title{margin-bottom:2.1rem;margin-top:3rem}.multiselect-custom{max-width:71.1rem}.content-install{margin-top:3.7rem}.home-page-inner-wrap{margin:0 auto;max-width:91rem}.setup-home-title{margin-bottom:3.9rem;padding-top:1.8rem;text-align:center}.setup-home-item{background-color:#fafafa;border:1px solid #ccc;color:#333;display:block;margin-bottom:2rem;margin-left:1.3rem;margin-right:1.3rem;min-height:30rem;padding:2rem;text-align:center}.setup-home-item:hover{border-color:#8c8c8c;color:#333;text-decoration:none;transition:border-color .1s linear}.setup-home-item:active{-ms-transform:scale(0.99);transform:scale(0.99)}.setup-home-item:before{display:block;font-size:7rem;margin-bottom:3.3rem;margin-top:4rem}.setup-home-item-component:before,.setup-home-item-extension:before{content:'\e612'}.setup-home-item-module:before{content:'\e647'}.setup-home-item-upgrade:before{content:'\e614'}.setup-home-item-configuration:before{content:'\e610'}.setup-home-item-title{display:block;font-size:1.8rem;letter-spacing:.025em;margin-bottom:1rem}.setup-home-item-description{display:block}.extension-manager-wrap{border:1px solid #bbb;margin:0 0 4rem}.extension-manager-account{font-size:2.1rem;display:inline-block;font-weight:400}.extension-manager-title{font-size:3.2rem;background-color:#f8f8f8;border-bottom:1px solid #e3e3e3;color:#41362f;font-weight:600;line-height:1.2;padding:2rem}.extension-manager-content{padding:2.5rem 2rem 2rem}.extension-manager-items{list-style:none;margin:0;text-align:center}.extension-manager-items .btn{border:1px solid #adadad;display:block;margin:1rem auto 0}.extension-manager-items .item-title{font-size:2.1rem;display:inline-block;text-align:left}.extension-manager-items .item-number{font-size:4.1rem;display:inline-block;line-height:.8;margin:0 5px 1.5rem 0;vertical-align:top}.extension-manager-items .item-date{font-size:2.6rem;margin-top:1px}.extension-manager-items .item-date-title{font-size:1.5rem}.extension-manager-items .item-install{margin:0 0 2rem}.sync-login-wrap{padding:0 10% 4rem}.sync-login-wrap .legend{font-size:2.6rem;color:#eb5202;float:left;font-weight:300;line-height:1.2;margin:-1rem 0 2.5rem;position:static;width:100%}.sync-login-wrap .legend._hidden{display:none}.sync-login-wrap .login-header{font-size:3.4rem;font-weight:300;margin:0 0 2rem}.sync-login-wrap .login-header span{display:inline-block;padding:.9rem 0 0;vertical-align:top}.sync-login-wrap h4{font-size:1.4rem;margin:0 0 2rem}.sync-login-wrap .sync-login-steps{margin:0 0 2rem 1.5rem}.sync-login-wrap .sync-login-steps li{padding:0 0 0 1rem}.sync-login-wrap .form-row .form-label{display:inline-block}.sync-login-wrap .form-row .form-label.required{padding-left:1.5rem}.sync-login-wrap .form-row .form-label.required:after{left:0;position:absolute;right:auto}.sync-login-wrap .form-row{max-width:28rem}.sync-login-wrap .form-actions{display:table;margin-top:-1.3rem}.sync-login-wrap .form-actions .links{display:table-header-group}.sync-login-wrap .form-actions .actions{padding:3rem 0 0}@media all and (max-width:1047px){.admin__menu .submenu li{min-width:19.8rem}.nav{padding-bottom:5.38rem;padding-left:1.5rem;text-align:center}.nav-bar{display:inline-block;float:none;margin-right:0;vertical-align:top}.nav .btn-group,.nav-bar-outer-actions{display:inline-block;float:none;margin-top:-8.48rem;text-align:center;vertical-align:top;width:100%}.nav-bar-outer-actions{padding-right:0}.nav-bar-outer-actions .outer-actions-inner-wrap{display:inline-block}.app-updater .nav{padding-bottom:1.7rem}.app-updater .nav-bar-outer-actions{margin-top:2rem}}@media all and (min-width:768px){.page-layout-admin-2columns-left .page-columns{margin-left:-30px}.page-layout-admin-2columns-left .page-columns:after{clear:both;content:'';display:table}.page-layout-admin-2columns-left .page-columns .main-col{width:calc((100%) * .75 - 30px);float:right}.page-layout-admin-2columns-left .page-columns .side-col{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}.col-m-1,.col-m-10,.col-m-11,.col-m-12,.col-m-2,.col-m-3,.col-m-4,.col-m-5,.col-m-6,.col-m-7,.col-m-8,.col-m-9{float:left}.col-m-12{width:100%}.col-m-11{width:91.66666667%}.col-m-10{width:83.33333333%}.col-m-9{width:75%}.col-m-8{width:66.66666667%}.col-m-7{width:58.33333333%}.col-m-6{width:50%}.col-m-5{width:41.66666667%}.col-m-4{width:33.33333333%}.col-m-3{width:25%}.col-m-2{width:16.66666667%}.col-m-1{width:8.33333333%}.col-m-pull-12{right:100%}.col-m-pull-11{right:91.66666667%}.col-m-pull-10{right:83.33333333%}.col-m-pull-9{right:75%}.col-m-pull-8{right:66.66666667%}.col-m-pull-7{right:58.33333333%}.col-m-pull-6{right:50%}.col-m-pull-5{right:41.66666667%}.col-m-pull-4{right:33.33333333%}.col-m-pull-3{right:25%}.col-m-pull-2{right:16.66666667%}.col-m-pull-1{right:8.33333333%}.col-m-pull-0{right:auto}.col-m-push-12{left:100%}.col-m-push-11{left:91.66666667%}.col-m-push-10{left:83.33333333%}.col-m-push-9{left:75%}.col-m-push-8{left:66.66666667%}.col-m-push-7{left:58.33333333%}.col-m-push-6{left:50%}.col-m-push-5{left:41.66666667%}.col-m-push-4{left:33.33333333%}.col-m-push-3{left:25%}.col-m-push-2{left:16.66666667%}.col-m-push-1{left:8.33333333%}.col-m-push-0{left:auto}.col-m-offset-12{margin-left:100%}.col-m-offset-11{margin-left:91.66666667%}.col-m-offset-10{margin-left:83.33333333%}.col-m-offset-9{margin-left:75%}.col-m-offset-8{margin-left:66.66666667%}.col-m-offset-7{margin-left:58.33333333%}.col-m-offset-6{margin-left:50%}.col-m-offset-5{margin-left:41.66666667%}.col-m-offset-4{margin-left:33.33333333%}.col-m-offset-3{margin-left:25%}.col-m-offset-2{margin-left:16.66666667%}.col-m-offset-1{margin-left:8.33333333%}.col-m-offset-0{margin-left:0}.page-columns{margin-left:-30px}.page-columns:after{clear:both;content:'';display:table}.page-columns .page-inner-content{width:calc((100%) * .75 - 30px);float:right}.page-columns .page-inner-sidebar{width:calc((100%) * .25 - 30px);float:left;margin-left:30px}}@media all and (min-width:1048px){.col-l-1,.col-l-10,.col-l-11,.col-l-12,.col-l-2,.col-l-3,.col-l-4,.col-l-5,.col-l-6,.col-l-7,.col-l-8,.col-l-9{float:left}.col-l-12{width:100%}.col-l-11{width:91.66666667%}.col-l-10{width:83.33333333%}.col-l-9{width:75%}.col-l-8{width:66.66666667%}.col-l-7{width:58.33333333%}.col-l-6{width:50%}.col-l-5{width:41.66666667%}.col-l-4{width:33.33333333%}.col-l-3{width:25%}.col-l-2{width:16.66666667%}.col-l-1{width:8.33333333%}.col-l-pull-12{right:100%}.col-l-pull-11{right:91.66666667%}.col-l-pull-10{right:83.33333333%}.col-l-pull-9{right:75%}.col-l-pull-8{right:66.66666667%}.col-l-pull-7{right:58.33333333%}.col-l-pull-6{right:50%}.col-l-pull-5{right:41.66666667%}.col-l-pull-4{right:33.33333333%}.col-l-pull-3{right:25%}.col-l-pull-2{right:16.66666667%}.col-l-pull-1{right:8.33333333%}.col-l-pull-0{right:auto}.col-l-push-12{left:100%}.col-l-push-11{left:91.66666667%}.col-l-push-10{left:83.33333333%}.col-l-push-9{left:75%}.col-l-push-8{left:66.66666667%}.col-l-push-7{left:58.33333333%}.col-l-push-6{left:50%}.col-l-push-5{left:41.66666667%}.col-l-push-4{left:33.33333333%}.col-l-push-3{left:25%}.col-l-push-2{left:16.66666667%}.col-l-push-1{left:8.33333333%}.col-l-push-0{left:auto}.col-l-offset-12{margin-left:100%}.col-l-offset-11{margin-left:91.66666667%}.col-l-offset-10{margin-left:83.33333333%}.col-l-offset-9{margin-left:75%}.col-l-offset-8{margin-left:66.66666667%}.col-l-offset-7{margin-left:58.33333333%}.col-l-offset-6{margin-left:50%}.col-l-offset-5{margin-left:41.66666667%}.col-l-offset-4{margin-left:33.33333333%}.col-l-offset-3{margin-left:25%}.col-l-offset-2{margin-left:16.66666667%}.col-l-offset-1{margin-left:8.33333333%}.col-l-offset-0{margin-left:0}}@media all and (min-width:1440px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{float:left}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-pull-12{right:100%}.col-xl-pull-11{right:91.66666667%}.col-xl-pull-10{right:83.33333333%}.col-xl-pull-9{right:75%}.col-xl-pull-8{right:66.66666667%}.col-xl-pull-7{right:58.33333333%}.col-xl-pull-6{right:50%}.col-xl-pull-5{right:41.66666667%}.col-xl-pull-4{right:33.33333333%}.col-xl-pull-3{right:25%}.col-xl-pull-2{right:16.66666667%}.col-xl-pull-1{right:8.33333333%}.col-xl-pull-0{right:auto}.col-xl-push-12{left:100%}.col-xl-push-11{left:91.66666667%}.col-xl-push-10{left:83.33333333%}.col-xl-push-9{left:75%}.col-xl-push-8{left:66.66666667%}.col-xl-push-7{left:58.33333333%}.col-xl-push-6{left:50%}.col-xl-push-5{left:41.66666667%}.col-xl-push-4{left:33.33333333%}.col-xl-push-3{left:25%}.col-xl-push-2{left:16.66666667%}.col-xl-push-1{left:8.33333333%}.col-xl-push-0{left:auto}.col-xl-offset-12{margin-left:100%}.col-xl-offset-11{margin-left:91.66666667%}.col-xl-offset-10{margin-left:83.33333333%}.col-xl-offset-9{margin-left:75%}.col-xl-offset-8{margin-left:66.66666667%}.col-xl-offset-7{margin-left:58.33333333%}.col-xl-offset-6{margin-left:50%}.col-xl-offset-5{margin-left:41.66666667%}.col-xl-offset-4{margin-left:33.33333333%}.col-xl-offset-3{margin-left:25%}.col-xl-offset-2{margin-left:16.66666667%}.col-xl-offset-1{margin-left:8.33333333%}.col-xl-offset-0{margin-left:0}}@media all and (max-width:767px){.abs-clearer-mobile:after,.nav-bar:after{clear:both;content:'';display:table}.list-definition>dt{float:none}.list-definition>dd{margin-left:0}.form-row .form-label{text-align:left}.form-row .form-label.required:after{position:static}.nav{padding-bottom:0;padding-left:0;padding-right:0}.nav-bar-outer-actions{margin-top:0}.nav-bar{display:block;margin-bottom:0;margin-left:auto;margin-right:auto;width:30.9rem}.nav-bar:before{display:none}.nav-bar>li{float:left;min-height:9rem}.nav-bar>li:after{display:none}.nav-bar>li:nth-child(4n){clear:both}.nav-bar a{line-height:1.4}.tooltip{display:none!important}.readiness-check-content{margin-right:2rem}.readiness-check-side{padding:2rem 0;position:static}.form-el-insider,.form-el-insider-wrap,.page-web-configuration .form-el-insider-input,.page-web-configuration .form-el-insider-input .form-el-input{display:block;width:100%}}@media all and (max-width:479px){.nav-bar{width:23.175rem}.nav-bar>li{width:7.725rem}.nav .btn-group .btn-wrap-try-again,.nav-bar-outer-actions .btn-wrap-try-again{clear:both;display:block;float:none;margin-left:auto;margin-right:auto;margin-top:1rem;padding-top:1rem}} diff --git a/setup/src/Magento/Setup/Console/Command/InfoBackupsListCommand.php b/setup/src/Magento/Setup/Console/Command/InfoBackupsListCommand.php index 178102aa0b3b7..94337dd0742e3 100644 --- a/setup/src/Magento/Setup/Console/Command/InfoBackupsListCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InfoBackupsListCommand.php @@ -11,8 +11,10 @@ use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Setup\BackupRollback; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableFactory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\App\ObjectManager; /** * Command prints list of available backup files @@ -33,16 +35,24 @@ class InfoBackupsListCommand extends Command */ private $directoryList; + /** + * @var TableFactory + */ + private $tableHelperFactory; + /** * @param DirectoryList $directoryList * @param File $file + * @param TableFactory $tableHelperFactory */ public function __construct( DirectoryList $directoryList, - File $file + File $file, + TableFactory $tableHelperFactory = null ) { $this->directoryList = $directoryList; $this->file = $file; + $this->tableHelperFactory = $tableHelperFactory ?: ObjectManager::getInstance()->create(TableFactory::class); parent::__construct(); } @@ -90,14 +100,14 @@ protected function execute(InputInterface $input, OutputInterface $output) return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } $output->writeln("<info>Showing backup files in $backupsDir.</info>"); - /** @var \Symfony\Component\Console\Helper\Table $table */ - $table = $this->getHelperSet()->get('table'); - $table->setHeaders(['Backup Filename', 'Backup Type']); + /** @var \Symfony\Component\Console\Helper\Table $tableHelper */ + $tableHelper = $this->tableHelperFactory->create(['output' => $output]); + $tableHelper->setHeaders(['Backup Filename', 'Backup Type']); asort($tempTable); foreach ($tempTable as $key => $value) { - $table->addRow([$key, $value]); + $tableHelper->addRow([$key, $value]); } - $table->render($output); + $tableHelper->render(); } else { $output->writeln('<info>No backup files found.</info>'); } diff --git a/setup/src/Magento/Setup/Console/Command/InfoCurrencyListCommand.php b/setup/src/Magento/Setup/Console/Command/InfoCurrencyListCommand.php index d673fc85c1ef1..91cfb5e7f6b5c 100644 --- a/setup/src/Magento/Setup/Console/Command/InfoCurrencyListCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InfoCurrencyListCommand.php @@ -6,10 +6,12 @@ namespace Magento\Setup\Console\Command; +use Symfony\Component\Console\Helper\TableFactory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; use Magento\Framework\Setup\Lists; +use Magento\Framework\App\ObjectManager; /** * Command prints list of available currencies @@ -23,12 +25,19 @@ class InfoCurrencyListCommand extends Command */ private $lists; + /** + * @var TableFactory + */ + private $tableHelperFactory; + /** * @param Lists $lists + * @param TableFactory $tableHelperFactory */ - public function __construct(Lists $lists) + public function __construct(Lists $lists, TableFactory $tableHelperFactory = null) { $this->lists = $lists; + $this->tableHelperFactory = $tableHelperFactory ?: ObjectManager::getInstance()->create(TableFactory::class); parent::__construct(); } @@ -48,14 +57,14 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $table = $this->getHelperSet()->get('table'); - $table->setHeaders(['Currency', 'Code']); + $tableHelper = $this->tableHelperFactory->create(['output' => $output]); + $tableHelper->setHeaders(['Currency', 'Code']); foreach ($this->lists->getCurrencyList() as $key => $currency) { - $table->addRow([$currency, $key]); + $tableHelper->addRow([$currency, $key]); } - $table->render($output); + $tableHelper->render(); return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } } diff --git a/setup/src/Magento/Setup/Console/Command/InfoLanguageListCommand.php b/setup/src/Magento/Setup/Console/Command/InfoLanguageListCommand.php index 88d1bdd5601d5..8950bd5edb2fa 100644 --- a/setup/src/Magento/Setup/Console/Command/InfoLanguageListCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InfoLanguageListCommand.php @@ -6,8 +6,10 @@ namespace Magento\Setup\Console\Command; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Setup\Lists; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\TableFactory; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -23,12 +25,19 @@ class InfoLanguageListCommand extends Command */ private $lists; + /** + * @var TableFactory + */ + private $tableHelperFactory; + /** * @param Lists $lists + * @param TableFactory $tableHelperFactory */ - public function __construct(Lists $lists) + public function __construct(Lists $lists, TableFactory $tableHelperFactory = null) { $this->lists = $lists; + $this->tableHelperFactory = $tableHelperFactory ?: ObjectManager::getInstance()->create(TableFactory::class); parent::__construct(); } @@ -48,14 +57,14 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $table = $this->getHelperSet()->get('table'); - $table->setHeaders(['Language', 'Code']); + $tableHelper = $this->tableHelperFactory->create(['output' => $output]); + $tableHelper->setHeaders(['Language', 'Code']); foreach ($this->lists->getLocaleList() as $key => $locale) { - $table->addRow([$locale, $key]); + $tableHelper->addRow([$locale, $key]); } - $table->render($output); + $tableHelper->render(); return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } } diff --git a/setup/src/Magento/Setup/Console/Command/InfoTimezoneListCommand.php b/setup/src/Magento/Setup/Console/Command/InfoTimezoneListCommand.php index 95b4cd27bbd3a..2ff1d228dfe24 100644 --- a/setup/src/Magento/Setup/Console/Command/InfoTimezoneListCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InfoTimezoneListCommand.php @@ -10,6 +10,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Command\Command; use Magento\Framework\Setup\Lists; +use Symfony\Component\Console\Helper\TableFactory; +use Magento\Framework\App\ObjectManager; /** * Command prints list of available timezones @@ -23,12 +25,19 @@ class InfoTimezoneListCommand extends Command */ private $lists; + /** + * @var TableFactory + */ + private $tableHelperFactory; + /** * @param Lists $lists + * @param TableFactory $tableHelperFactory */ - public function __construct(Lists $lists) + public function __construct(Lists $lists, TableFactory $tableHelperFactory = null) { $this->lists = $lists; + $this->tableHelperFactory = $tableHelperFactory ?: ObjectManager::getInstance()->create(TableFactory::class); parent::__construct(); } @@ -48,14 +57,14 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $table = $this->getHelperSet()->get('table'); - $table->setHeaders(['Timezone', 'Code']); + $tableHelper = $this->tableHelperFactory->create(['output' => $output]); + $tableHelper->setHeaders(['Timezone', 'Code']); foreach ($this->lists->getTimezoneList() as $key => $timezone) { - $table->addRow([$timezone, $key]); + $tableHelper->addRow([$timezone, $key]); } - $table->render($output); + $tableHelper->render(); return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } } diff --git a/setup/src/Magento/Setup/Console/Command/InstallCommand.php b/setup/src/Magento/Setup/Console/Command/InstallCommand.php index 65eb047a5c77e..d94beff15b396 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallCommand.php @@ -50,7 +50,7 @@ class InstallCommand extends AbstractSetupCommand /** * List of comma-separated module names. That must be avoided during installation. * List of comma-separated module names. That must be avoided during installation. - * Avaiable magic param all. + * Available magic param all. */ const INPUT_KEY_DISABLE_MODULES = 'disable_modules'; @@ -166,14 +166,14 @@ protected function configure() null, InputOption::VALUE_OPTIONAL, 'List of comma-separated module names. That must be included during installation. ' - . 'Avaiable magic param "all".' + . 'Available magic param "all".' ), new InputOption( self::INPUT_KEY_DISABLE_MODULES, null, InputOption::VALUE_OPTIONAL, 'List of comma-separated module names. That must be avoided during installation. ' - . 'Avaiable magic param "all".' + . 'Available magic param "all".' ), new InputOption( self::CONVERT_OLD_SCRIPTS_KEY, diff --git a/setup/src/Magento/Setup/Console/Command/ModuleStatusCommand.php b/setup/src/Magento/Setup/Console/Command/ModuleStatusCommand.php index 85af8f3caeb1a..65fc265a64ec8 100644 --- a/setup/src/Magento/Setup/Console/Command/ModuleStatusCommand.php +++ b/setup/src/Magento/Setup/Console/Command/ModuleStatusCommand.php @@ -3,11 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Setup\Console\Command; +use Magento\Framework\Module\FullModuleList; +use Magento\Framework\Module\ModuleList; use Magento\Setup\Model\ObjectManagerProvider; +use Magento\Framework\Console\Cli; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; /** * Command for displaying status of modules @@ -38,7 +45,10 @@ public function __construct(ObjectManagerProvider $objectManagerProvider) protected function configure() { $this->setName('module:status') - ->setDescription('Displays status of modules'); + ->setDescription('Displays status of modules') + ->addArgument('module', InputArgument::OPTIONAL, 'Optional module name') + ->addOption('enabled', null, null, 'Print only enabled modules') + ->addOption('disabled', null, null, 'Print only disabled modules'); parent::configure(); } @@ -47,24 +57,106 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $moduleList = $this->objectManagerProvider->get()->create(\Magento\Framework\Module\ModuleList::class); - $output->writeln('<info>List of enabled modules:</info>'); - $enabledModules = $moduleList->getNames(); - if (count($enabledModules) === 0) { - $output->writeln('None'); - } else { - $output->writeln(join("\n", $enabledModules)); + $moduleName = (string)$input->getArgument('module'); + if ($moduleName) { + return $this->showSpecificModule($moduleName, $output); } + + $onlyEnabled = $input->getOption('enabled'); + if ($onlyEnabled) { + return $this->showEnabledModules($output); + } + + $onlyDisabled = $input->getOption('disabled'); + if ($onlyDisabled) { + return $this->showDisabledModules($output); + } + + $output->writeln('<info>List of enabled modules:</info>'); + $this->showEnabledModules($output); $output->writeln(''); - $fullModuleList = $this->objectManagerProvider->get()->create(\Magento\Framework\Module\FullModuleList::class); $output->writeln("<info>List of disabled modules:</info>"); - $disabledModules = array_diff($fullModuleList->getNames(), $enabledModules); - if (count($disabledModules) === 0) { + $this->showDisabledModules($output); + $output->writeln(''); + } + + /** + * @param string $moduleName + * @param OutputInterface $output + */ + private function showSpecificModule(string $moduleName, OutputInterface $output) + { + $allModules = $this->getAllModules(); + if (!in_array($moduleName, $allModules->getNames())) { + $output->writeln('<error>Module does not exist</error>'); + return Cli::RETURN_FAILURE; + } + + $enabledModules = $this->getEnabledModules(); + if (in_array($moduleName, $enabledModules->getNames())) { + $output->writeln('<info>Module is enabled</info>'); + return Cli::RETURN_FAILURE; + } + + $output->writeln('<info>Module is disabled</info>'); + return \Magento\Framework\Console\Cli::RETURN_SUCCESS; + } + + /** + * @param OutputInterface $output + */ + private function showEnabledModules(OutputInterface $output) + { + $enabledModules = $this->getEnabledModules(); + $enabledModuleNames = $enabledModules->getNames(); + if (count($enabledModuleNames) === 0) { $output->writeln('None'); - } else { - $output->writeln(join("\n", $disabledModules)); + return Cli::RETURN_FAILURE; } + + $output->writeln(join("\n", $enabledModuleNames)); return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } + + /** + * @param OutputInterface $output + */ + private function showDisabledModules(OutputInterface $output) + { + $disabledModuleNames = $this->getDisabledModuleNames(); + if (count($disabledModuleNames) === 0) { + $output->writeln('None'); + return Cli::RETURN_FAILURE; + } + + $output->writeln(join("\n", $disabledModuleNames)); + return \Magento\Framework\Console\Cli::RETURN_SUCCESS; + } + + /** + * @return FullModuleList + */ + private function getAllModules(): FullModuleList + { + return $this->objectManagerProvider->get()->create(FullModuleList::class); + } + + /** + * @return ModuleList + */ + private function getEnabledModules(): ModuleList + { + return $this->objectManagerProvider->get()->create(ModuleList::class); + } + + /** + * @return array + */ + private function getDisabledModuleNames(): array + { + $fullModuleList = $this->getAllModules(); + $enabledModules = $this->getEnabledModules(); + return array_diff($fullModuleList->getNames(), $enabledModules->getNames()); + } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php index 1ec9d486a5a22..2f19f010c1704 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php @@ -26,11 +26,13 @@ class Cache implements ConfigOptionsListInterface const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server'; const INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE = 'cache-backend-redis-db'; const INPUT_KEY_CACHE_BACKEND_REDIS_PORT = 'cache-backend-redis-port'; + const INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD = 'cache-backend-redis-password'; const CONFIG_PATH_CACHE_BACKEND = 'cache/frontend/default/backend'; const CONFIG_PATH_CACHE_BACKEND_SERVER = 'cache/frontend/default/backend_options/server'; const CONFIG_PATH_CACHE_BACKEND_DATABASE = 'cache/frontend/default/backend_options/database'; const CONFIG_PATH_CACHE_BACKEND_PORT = 'cache/frontend/default/backend_options/port'; + const CONFIG_PATH_CACHE_BACKEND_PASSWORD = 'cache/frontend/default/backend_options/password'; /** * @var array @@ -38,7 +40,8 @@ class Cache implements ConfigOptionsListInterface private $defaultConfigValues = [ self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => '0', - self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => '6379' + self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => '6379', + self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => '' ]; /** @@ -55,6 +58,7 @@ class Cache implements ConfigOptionsListInterface self::INPUT_KEY_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_CACHE_BACKEND_SERVER, self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_CACHE_BACKEND_DATABASE, self::INPUT_KEY_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_CACHE_BACKEND_PORT, + self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_CACHE_BACKEND_PASSWORD ]; /** @@ -102,12 +106,18 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_CACHE_BACKEND_PORT, 'Redis server listen port' + ), + new TextConfigOption( + self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, + 'Redis server password' ) ]; } /** - * {@inheritdoc} + * @inheritdoc */ public function createConfig(array $options, DeploymentConfig $deploymentConfig) { @@ -131,7 +141,7 @@ public function createConfig(array $options, DeploymentConfig $deploymentConfig) } /** - * {@inheritdoc} + * @inheritdoc */ public function validate(array $options, DeploymentConfig $deploymentConfig) { @@ -190,6 +200,13 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen self::CONFIG_PATH_CACHE_BACKEND_DATABASE, $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE) ); + + $config['password'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD]) + ? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD] + : $deploymentConfig->get( + self::CONFIG_PATH_CACHE_BACKEND_PASSWORD, + $this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD) + ); return $this->redisValidator->isValidConnection($config); } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php index be1cc5b010185..c288b4dd51d65 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php @@ -27,12 +27,14 @@ class PageCache implements ConfigOptionsListInterface const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE = 'page-cache-redis-db'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT = 'page-cache-redis-port'; const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA = 'page-cache-redis-compress-data'; + const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD = 'page-cache-redis-password'; const CONFIG_PATH_PAGE_CACHE_BACKEND = 'cache/frontend/page_cache/backend'; const CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER = 'cache/frontend/page_cache/backend_options/server'; const CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE = 'cache/frontend/page_cache/backend_options/database'; const CONFIG_PATH_PAGE_CACHE_BACKEND_PORT = 'cache/frontend/page_cache/backend_options/port'; const CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA = 'cache/frontend/page_cache/backend_options/compress_data'; + const CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD = 'cache/frontend/page_cache/backend_options/password'; /** * @var array @@ -41,7 +43,8 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => '127.0.0.1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => '1', self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => '6379', - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => '0' + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => '0', + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => '' ]; /** @@ -58,7 +61,8 @@ class PageCache implements ConfigOptionsListInterface self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER => self::CONFIG_PATH_PAGE_CACHE_BACKEND_SERVER, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE => self::CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE, self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PORT => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PORT, - self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_COMPRESS_DATA => self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA, + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD => self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD ]; /** @@ -112,12 +116,18 @@ public function getOptions() TextConfigOption::FRONTEND_WIZARD_TEXT, self::CONFIG_PATH_PAGE_CACHE_BACKEND_COMPRESS_DATA, 'Set to 1 to compress the full page cache (use 0 to disable)' + ), + new TextConfigOption( + self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD, + TextConfigOption::FRONTEND_WIZARD_TEXT, + self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, + 'Redis server password' ) ]; } /** - * {@inheritdoc} + * @inheritdoc */ public function createConfig(array $options, DeploymentConfig $deploymentConfig) { @@ -142,7 +152,7 @@ public function createConfig(array $options, DeploymentConfig $deploymentConfig) } /** - * {@inheritdoc} + * @inheritdoc */ public function validate(array $options, DeploymentConfig $deploymentConfig) { @@ -201,6 +211,13 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen self::CONFIG_PATH_PAGE_CACHE_BACKEND_DATABASE, $this->getDefaultConfigValue(self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_DATABASE) ); + + $config['password'] = isset($options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD]) + ? $options[self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD] + : $deploymentConfig->get( + self::CONFIG_PATH_PAGE_CACHE_BACKEND_PASSWORD, + $this->getDefaultConfigValue(self::INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_PASSWORD) + ); return $this->redisValidator->isValidConnection($config); } diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php index a7dc4dd1e6099..a1a62dc594683 100644 --- a/setup/src/Magento/Setup/Model/Installer.php +++ b/setup/src/Magento/Setup/Model/Installer.php @@ -815,6 +815,11 @@ public function declarativeInstallSchema(array $request) */ public function installSchema(array $request) { + /** @var \Magento\Framework\Registry $registry */ + $registry = $this->objectManagerProvider->get()->get(\Magento\Framework\Registry::class); + //For backward compatibility in install and upgrade scripts with enabled parallelization. + $registry->register('setup-mode-enabled', true); + $this->assertDbConfigExists(); $this->assertDbAccessible(); $setup = $this->setupFactory->create($this->context->getResources()); @@ -831,6 +836,8 @@ public function installSchema(array $request) $schemaListener->setResource('default'); $this->schemaPersistor->persist($schemaListener); } + + $registry->unregister('setup-mode-enabled'); } /** @@ -853,12 +860,19 @@ private function convertationOfOldScriptsIsAllowed(array $request) */ public function installDataFixtures(array $request = []) { + /** @var \Magento\Framework\Registry $registry */ + $registry = $this->objectManagerProvider->get()->get(\Magento\Framework\Registry::class); + //For backward compatibility in install and upgrade scripts with enabled parallelization. + $registry->register('setup-mode-enabled', true); + $this->assertDbConfigExists(); $this->assertDbAccessible(); $setup = $this->dataSetupFactory->create(); $this->checkFilePermissionsForDbUpgrade(); $this->log->log('Data install/update:'); $this->handleDBSchemaData($setup, 'data', $request); + + $registry->unregister('setup-mode-enabled'); } /** diff --git a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php index c2bdafa8df82e..2c4967c4c2ffd 100644 --- a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php +++ b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php @@ -286,7 +286,7 @@ private function checkPopulateRawPostSetting() $data = []; $error = false; - $iniSetting = intVal(ini_get('always_populate_raw_post_data')); + $iniSetting = intval(ini_get('always_populate_raw_post_data')); $checkVersionConstraint = $this->versionParser->parseConstraints('~5.6.0'); $normalizedPhpVersion = $this->getNormalizedCurrentPhpVersion(PHP_VERSION); @@ -302,7 +302,7 @@ private function checkPopulateRawPostSetting() Please open your php.ini file and set always_populate_raw_post_data to -1. If you need more help please call your hosting provider.', PHP_VERSION, - intVal(ini_get('always_populate_raw_post_data')) + intval(ini_get('always_populate_raw_post_data')) ); $data['always_populate_raw_post_data'] = [ diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoBackupsListCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoBackupsListCommandTest.php index ff4722ca0fd4a..1329324b74015 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoBackupsListCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoBackupsListCommandTest.php @@ -16,9 +16,9 @@ public function testExecute() $table = $this->createMock(\Symfony\Component\Console\Helper\Table::class); $table->expects($this->once())->method('setHeaders')->with(['Backup Filename', 'Backup Type']); $table->expects($this->once())->method('addRow')->with(['backupFile_media.tgz', 'media']); - /** @var \Symfony\Component\Console\Helper\HelperSet|\PHPUnit_Framework_MockObject_MockObject $helperSet */ - $helperSet = $this->createMock(\Symfony\Component\Console\Helper\HelperSet::class); - $helperSet->expects($this->once())->method('get')->with('table')->will($this->returnValue($table)); + /** @var \Symfony\Component\Console\Helper\TableFactory|\PHPUnit_Framework_MockObject_MockObject $helperSet */ + $tableFactoryMock = $this->createMock(\Symfony\Component\Console\Helper\TableFactory::class); + $tableFactoryMock->expects($this->once())->method('create')->will($this->returnValue($table)); /** @var \Magento\Framework\App\Filesystem\DirectoryList * |\PHPUnit_Framework_MockObject_MockObject $directoryList */ @@ -29,8 +29,7 @@ public function testExecute() $file->expects($this->once()) ->method('readDirectoryRecursively') ->will($this->returnValue(['backupFile_media.tgz'])); - $command = new InfoBackupsListCommand($directoryList, $file); - $command->setHelperSet($helperSet); + $command = new InfoBackupsListCommand($directoryList, $file, $tableFactoryMock); $commandTester = new CommandTester($command); $commandTester->execute([]); $expected = 'Showing backup files in '; diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoCurrencyListCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoCurrencyListCommandTest.php index e3e0a1de71727..caee4af9c7fc4 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoCurrencyListCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoCurrencyListCommandTest.php @@ -21,15 +21,14 @@ public function testExecute() $table->expects($this->once())->method('setHeaders')->with(['Currency', 'Code']); $table->expects($this->once())->method('addRow')->with(['Currency description', 'CUR']); - /** @var \Symfony\Component\Console\Helper\HelperSet|\PHPUnit_Framework_MockObject_MockObject $helperSet */ - $helperSet = $this->createMock(\Symfony\Component\Console\Helper\HelperSet::class); - $helperSet->expects($this->once())->method('get')->with('table')->will($this->returnValue($table)); + /** @var \Symfony\Component\Console\Helper\TableFactory|\PHPUnit_Framework_MockObject_MockObject $helperSet */ + $tableFactoryMock = $this->createMock(\Symfony\Component\Console\Helper\TableFactory::class); + $tableFactoryMock->expects($this->once())->method('create')->will($this->returnValue($table)); /** @var \Magento\Framework\Setup\Lists|\PHPUnit_Framework_MockObject_MockObject $list */ $list = $this->createMock(\Magento\Framework\Setup\Lists::class); $list->expects($this->once())->method('getCurrencyList')->will($this->returnValue($currencies)); - $command = new InfoCurrencyListCommand($list); - $command->setHelperSet($helperSet); + $command = new InfoCurrencyListCommand($list, $tableFactoryMock); $commandTester = new CommandTester($command); $commandTester->execute([]); } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoLanguageListCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoLanguageListCommandTest.php index f1622b59c4593..d9c899b178c37 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoLanguageListCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoLanguageListCommandTest.php @@ -21,15 +21,14 @@ public function testExecute() $table->expects($this->once())->method('setHeaders')->with(['Language', 'Code']); $table->expects($this->once())->method('addRow')->with(['Language description', 'LNG']); - /** @var \Symfony\Component\Console\Helper\HelperSet|\PHPUnit_Framework_MockObject_MockObject $helperSet */ - $helperSet = $this->createMock(\Symfony\Component\Console\Helper\HelperSet::class); - $helperSet->expects($this->once())->method('get')->with('table')->will($this->returnValue($table)); + /** @var \Symfony\Component\Console\Helper\TableFactory|\PHPUnit_Framework_MockObject_MockObject $helperSet */ + $tableFactoryMock = $this->createMock(\Symfony\Component\Console\Helper\TableFactory::class); + $tableFactoryMock->expects($this->once())->method('create')->will($this->returnValue($table)); /** @var \Magento\Framework\Setup\Lists|\PHPUnit_Framework_MockObject_MockObject $list */ $list = $this->createMock(\Magento\Framework\Setup\Lists::class); $list->expects($this->once())->method('getLocaleList')->will($this->returnValue($languages)); - $command = new InfoLanguageListCommand($list); - $command->setHelperSet($helperSet); + $command = new InfoLanguageListCommand($list, $tableFactoryMock); $commandTester = new CommandTester($command); $commandTester->execute([]); } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoTimezoneListCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoTimezoneListCommandTest.php index 860862f304971..80871ecf634ed 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoTimezoneListCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InfoTimezoneListCommandTest.php @@ -21,15 +21,14 @@ public function testExecute() $table->expects($this->once())->method('setHeaders')->with(['Timezone', 'Code']); $table->expects($this->once())->method('addRow')->with(['timezone description', 'timezone']); - /** @var \Symfony\Component\Console\Helper\HelperSet|\PHPUnit_Framework_MockObject_MockObject $helperSet */ - $helperSet = $this->createMock(\Symfony\Component\Console\Helper\HelperSet::class); - $helperSet->expects($this->once())->method('get')->with('table')->will($this->returnValue($table)); + /** @var \Symfony\Component\Console\Helper\TableFactory|\PHPUnit_Framework_MockObject_MockObject $helperSet */ + $tableFactoryMock = $this->createMock(\Symfony\Component\Console\Helper\TableFactory::class); + $tableFactoryMock->expects($this->once())->method('create')->will($this->returnValue($table)); /** @var \Magento\Framework\Setup\Lists|\PHPUnit_Framework_MockObject_MockObject $list */ $list = $this->createMock(\Magento\Framework\Setup\Lists::class); $list->expects($this->once())->method('getTimezoneList')->will($this->returnValue($timezones)); - $command = new InfoTimezoneListCommand($list); - $command->setHelperSet($helperSet); + $command = new InfoTimezoneListCommand($list, $tableFactoryMock); $commandTester = new CommandTester($command); $commandTester->execute([]); } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php index ef0ea3e988364..1bbd2e671e59a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/CacheTest.php @@ -28,6 +28,9 @@ class CacheTest extends \PHPUnit\Framework\TestCase */ private $deploymentConfigMock; + /** + * Tests setup + */ protected function setUp() { $this->validatorMock = $this->createMock(RedisConnectionValidator::class); @@ -36,10 +39,13 @@ protected function setUp() $this->configOptionsList = new CacheConfigOptionsList($this->validatorMock); } + /** + * testGetOptions + */ public function testGetOptions() { $options = $this->configOptionsList->getOptions(); - $this->assertCount(4, $options); + $this->assertCount(5, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -56,8 +62,15 @@ public function testGetOptions() $this->assertArrayHasKey(3, $options); $this->assertInstanceOf(TextConfigOption::class, $options[3]); $this->assertEquals('cache-backend-redis-port', $options[3]->getName()); + + $this->assertArrayHasKey(4, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[4]); + $this->assertEquals('cache-backend-redis-password', $options[4]->getName()); } + /** + * testCreateConfigCacheRedis + */ public function testCreateConfigCacheRedis() { $this->deploymentConfigMock->method('get')->willReturn(''); @@ -70,7 +83,8 @@ public function testCreateConfigCacheRedis() 'backend_options' => [ 'server' => '', 'port' => '', - 'database' => '' + 'database' => '', + 'password' => '' ] ] ] @@ -82,6 +96,9 @@ public function testCreateConfigCacheRedis() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testCreateConfigWithRedisConfig + */ public function testCreateConfigWithRedisConfig() { $expectedConfigData = [ @@ -92,7 +109,8 @@ public function testCreateConfigWithRedisConfig() 'backend_options' => [ 'server' => 'localhost', 'port' => '1234', - 'database' => '5' + 'database' => '5', + 'password' => '' ] ] ] @@ -110,6 +128,9 @@ public function testCreateConfigWithRedisConfig() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testValidateWithValidInput + */ public function testValidateWithValidInput() { $options = [ @@ -118,7 +139,7 @@ public function testValidateWithValidInput() ]; $this->validatorMock->expects($this->once()) ->method('isValidConnection') - ->with(['host'=>'localhost', 'db'=>'', 'port'=>'']) + ->with(['host'=>'localhost', 'db'=>'', 'port'=>'', 'password'=>'']) ->willReturn(true); $errors = $this->configOptionsList->validate($options, $this->deploymentConfigMock); @@ -126,6 +147,9 @@ public function testValidateWithValidInput() $this->assertEmpty($errors); } + /** + * testValidateWithInvalidInput + */ public function testValidateWithInvalidInput() { $invalidCacheOption = 'clay-tablet'; diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php index e654bea9ac1c5..6f8e97cd5dd8d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/PageCacheTest.php @@ -28,6 +28,9 @@ class PageCacheTest extends \PHPUnit\Framework\TestCase */ private $deploymentConfigMock; + /** + * Test setup + */ protected function setUp() { $this->validatorMock = $this->createMock(RedisConnectionValidator::class, [], [], '', false); @@ -36,10 +39,13 @@ protected function setUp() $this->configList = new PageCache($this->validatorMock); } + /** + * testGetOptions + */ public function testGetOptions() { $options = $this->configList->getOptions(); - $this->assertCount(5, $options); + $this->assertCount(6, $options); $this->assertArrayHasKey(0, $options); $this->assertInstanceOf(SelectConfigOption::class, $options[0]); @@ -60,8 +66,15 @@ public function testGetOptions() $this->assertArrayHasKey(4, $options); $this->assertInstanceOf(TextConfigOption::class, $options[4]); $this->assertEquals('page-cache-redis-compress-data', $options[4]->getName()); + + $this->assertArrayHasKey(5, $options); + $this->assertInstanceOf(TextConfigOption::class, $options[5]); + $this->assertEquals('page-cache-redis-password', $options[5]->getName()); } + /** + * testCreateConfigWithRedis + */ public function testCreateConfigWithRedis() { $this->deploymentConfigMock->method('get')->willReturn(''); @@ -75,7 +88,8 @@ public function testCreateConfigWithRedis() 'server'=> '', 'port' => '', 'database' => '', - 'compress_data' => '' + 'compress_data' => '', + 'password' => '' ] ] ] @@ -87,6 +101,9 @@ public function testCreateConfigWithRedis() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testCreateConfigWithRedisConfiguration + */ public function testCreateConfigWithRedisConfiguration() { $expectedConfigData = [ @@ -98,7 +115,8 @@ public function testCreateConfigWithRedisConfiguration() 'server' => 'foo.bar', 'port' => '9000', 'database' => '6', - 'compress_data' => '1' + 'compress_data' => '1', + 'password' => '' ] ] ] @@ -118,6 +136,9 @@ public function testCreateConfigWithRedisConfiguration() $this->assertEquals($expectedConfigData, $configData->getData()); } + /** + * testValidationWithValidData + */ public function testValidationWithValidData() { $this->validatorMock->expects($this->once()) @@ -134,6 +155,9 @@ public function testValidationWithValidData() $this->assertEmpty($errors); } + /** + * testValidationWithInvalidData + */ public function testValidationWithInvalidData() { $options = [ diff --git a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php index 413094a1e0115..8a4263be18ecf 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php @@ -4,630 +4,642 @@ * See COPYING.txt for license details. */ -namespace Magento\Setup\Test\Unit\Model; - -use Magento\Backend\Setup\ConfigOptionsList; -use Magento\Framework\Config\ConfigOptionsListConstants; -use Magento\Framework\Setup\SchemaListener; -use Magento\Setup\Model\DeclarationInstaller; -use Magento\Setup\Model\Installer; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem\DriverPool; -use Magento\Framework\Config\File\ConfigFilePool; -use Magento\Framework\App\State\CleanupFiles; -use Magento\Framework\Setup\Patch\PatchApplier; -use Magento\Framework\Setup\Patch\PatchApplierFactory; -use Magento\Setup\Validator\DbValidator; - -/** - * @SuppressWarnings(PHPMD.TooManyFields) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class InstallerTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Setup\Model\Installer - */ - private $object; - - /** - * @var \Magento\Framework\Setup\FilePermissions|\PHPUnit_Framework_MockObject_MockObject - */ - private $filePermissions; - - /** - * @var \Magento\Framework\App\DeploymentConfig\Writer|\PHPUnit_Framework_MockObject_MockObject - */ - private $configWriter; - - /** - * @var \Magento\Framework\App\DeploymentConfig\Reader|\PHPUnit_Framework_MockObject_MockObject - */ - private $configReader; - - /** - * @var \Magento\Framework\App\DeploymentConfig|\PHPUnit_Framework_MockObject_MockObject - */ - private $config; - - /** - * @var \Magento\Framework\Module\ModuleListInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $moduleList; - - /** - * @var \Magento\Framework\Module\ModuleList\Loader|\PHPUnit_Framework_MockObject_MockObject - */ - private $moduleLoader; - - /** - * @var \Magento\Framework\App\Filesystem\DirectoryList|\PHPUnit_Framework_MockObject_MockObject - */ - private $directoryList; - - /** - * @var \Magento\Setup\Model\AdminAccountFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $adminFactory; - - /** - * @var \Magento\Framework\Setup\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $logger; - - /** - * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject - */ - private $random; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $connection; - - /** - * @var \Magento\Framework\App\MaintenanceMode|\PHPUnit_Framework_MockObject_MockObject - */ - private $maintenanceMode; - - /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject - */ - private $filesystem; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $objectManager; - - /** - * @var \Magento\Setup\Model\ConfigModel|\PHPUnit_Framework_MockObject_MockObject - */ - private $configModel; - - /** - * @var CleanupFiles|\PHPUnit_Framework_MockObject_MockObject - */ - private $cleanupFiles; - - /** - * @var DbValidator|\PHPUnit_Framework_MockObject_MockObject - */ - private $dbValidator; - - /** - * @var \Magento\Setup\Module\SetupFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $setupFactory; - - /** - * @var \Magento\Setup\Module\DataSetupFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $dataSetupFactory; - - /** - * @var \Magento\Framework\Setup\SampleData\State|\PHPUnit_Framework_MockObject_MockObject - */ - private $sampleDataState; - - /** - * @var \Magento\Framework\Component\ComponentRegistrar|\PHPUnit_Framework_MockObject_MockObject - */ - private $componentRegistrar; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Setup\Model\PhpReadinessCheck - */ - private $phpReadinessCheck; - - /** - * @var \Magento\Framework\Setup\DeclarationInstaller|\PHPUnit_Framework_MockObject_MockObject - */ - private $declarationInstallerMock; - - /** - * @var SchemaListener|\PHPUnit_Framework_MockObject_MockObject - */ - private $schemaListenerMock; - - /** - * Sample DB configuration segment - * - * @var array - */ - private static $dbConfig = [ - 'default' => [ - ConfigOptionsListConstants::KEY_HOST => '127.0.0.1', - ConfigOptionsListConstants::KEY_NAME => 'magento', - ConfigOptionsListConstants::KEY_USER => 'magento', - ConfigOptionsListConstants::KEY_PASSWORD => '', - ] - ]; - - /** - * @var \Magento\Framework\Model\ResourceModel\Db\Context|\PHPUnit_Framework_MockObject_MockObject - */ - private $contextMock; - - /** - * @var PatchApplier|\PHPUnit_Framework_MockObject_MockObject - */ - private $patchApplierMock; - - /** - * @var PatchApplierFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $patchApplierFactoryMock; - - protected function setUp() +namespace Magento\Setup\Test\Unit\Model { + + use Magento\Backend\Setup\ConfigOptionsList; + use Magento\Framework\Config\ConfigOptionsListConstants; + use Magento\Framework\Setup\SchemaListener; + use Magento\Setup\Model\DeclarationInstaller; + use Magento\Setup\Model\Installer; + use Magento\Framework\App\Filesystem\DirectoryList; + use Magento\Framework\Filesystem\DriverPool; + use Magento\Framework\Config\File\ConfigFilePool; + use Magento\Framework\App\State\CleanupFiles; + use Magento\Framework\Setup\Patch\PatchApplier; + use Magento\Framework\Setup\Patch\PatchApplierFactory; + use Magento\Setup\Validator\DbValidator; + + /** + * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ + class InstallerTest extends \PHPUnit\Framework\TestCase { - $this->filePermissions = $this->createMock(\Magento\Framework\Setup\FilePermissions::class); - $this->configWriter = $this->createMock(\Magento\Framework\App\DeploymentConfig\Writer::class); - $this->configReader = $this->createMock(\Magento\Framework\App\DeploymentConfig\Reader::class); - $this->config = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); - - $this->moduleList = $this->getMockForAbstractClass(\Magento\Framework\Module\ModuleListInterface::class); - $this->moduleList->expects($this->any())->method('getOne')->willReturn( - ['setup_version' => '2.0.0'] - ); - $this->moduleList->expects($this->any())->method('getNames')->willReturn( - ['Foo_One', 'Bar_Two'] - ); - $this->moduleLoader = $this->createMock(\Magento\Framework\Module\ModuleList\Loader::class); - $this->directoryList = - $this->createMock(\Magento\Framework\App\Filesystem\DirectoryList::class); - $this->adminFactory = $this->createMock(\Magento\Setup\Model\AdminAccountFactory::class); - $this->logger = $this->getMockForAbstractClass(\Magento\Framework\Setup\LoggerInterface::class); - $this->random = $this->createMock(\Magento\Framework\Math\Random::class); - $this->connection = $this->getMockForAbstractClass(\Magento\Framework\DB\Adapter\AdapterInterface::class); - $this->maintenanceMode = $this->createMock(\Magento\Framework\App\MaintenanceMode::class); - $this->filesystem = $this->createMock(\Magento\Framework\Filesystem::class); - $this->objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $this->contextMock = - $this->createMock(\Magento\Framework\Model\ResourceModel\Db\Context::class); - $this->configModel = $this->createMock(\Magento\Setup\Model\ConfigModel::class); - $this->cleanupFiles = $this->createMock(\Magento\Framework\App\State\CleanupFiles::class); - $this->dbValidator = $this->createMock(\Magento\Setup\Validator\DbValidator::class); - $this->setupFactory = $this->createMock(\Magento\Setup\Module\SetupFactory::class); - $this->dataSetupFactory = $this->createMock(\Magento\Setup\Module\DataSetupFactory::class); - $this->sampleDataState = $this->createMock(\Magento\Framework\Setup\SampleData\State::class); - $this->componentRegistrar = - $this->createMock(\Magento\Framework\Component\ComponentRegistrar::class); - $this->phpReadinessCheck = $this->createMock(\Magento\Setup\Model\PhpReadinessCheck::class); - $this->declarationInstallerMock = $this->createMock(DeclarationInstaller::class); - $this->schemaListenerMock = $this->createMock(SchemaListener::class); - $this->patchApplierFactoryMock = $this->createMock(PatchApplierFactory::class); - $this->patchApplierMock = $this->createMock(PatchApplier::class); - $this->patchApplierFactoryMock->expects($this->any())->method('create')->willReturn($this->patchApplierMock); - $this->object = $this->createObject(); - } + /** + * @var \Magento\Setup\Model\Installer + */ + private $object; + + /** + * @var \Magento\Framework\Setup\FilePermissions|\PHPUnit_Framework_MockObject_MockObject + */ + private $filePermissions; + + /** + * @var \Magento\Framework\App\DeploymentConfig\Writer|\PHPUnit_Framework_MockObject_MockObject + */ + private $configWriter; + + /** + * @var \Magento\Framework\App\DeploymentConfig\Reader|\PHPUnit_Framework_MockObject_MockObject + */ + private $configReader; + + /** + * @var \Magento\Framework\App\DeploymentConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $config; + + /** + * @var \Magento\Framework\Module\ModuleListInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $moduleList; + + /** + * @var \Magento\Framework\Module\ModuleList\Loader|\PHPUnit_Framework_MockObject_MockObject + */ + private $moduleLoader; + + /** + * @var \Magento\Framework\App\Filesystem\DirectoryList|\PHPUnit_Framework_MockObject_MockObject + */ + private $directoryList; + + /** + * @var \Magento\Setup\Model\AdminAccountFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $adminFactory; + + /** + * @var \Magento\Framework\Setup\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $logger; + + /** + * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject + */ + private $random; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $connection; + + /** + * @var \Magento\Framework\App\MaintenanceMode|\PHPUnit_Framework_MockObject_MockObject + */ + private $maintenanceMode; + + /** + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystem; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $objectManager; + + /** + * @var \Magento\Setup\Model\ConfigModel|\PHPUnit_Framework_MockObject_MockObject + */ + private $configModel; + + /** + * @var CleanupFiles|\PHPUnit_Framework_MockObject_MockObject + */ + private $cleanupFiles; + + /** + * @var DbValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $dbValidator; + + /** + * @var \Magento\Setup\Module\SetupFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $setupFactory; + + /** + * @var \Magento\Setup\Module\DataSetupFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataSetupFactory; + + /** + * @var \Magento\Framework\Setup\SampleData\State|\PHPUnit_Framework_MockObject_MockObject + */ + private $sampleDataState; + + /** + * @var \Magento\Framework\Component\ComponentRegistrar|\PHPUnit_Framework_MockObject_MockObject + */ + private $componentRegistrar; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Setup\Model\PhpReadinessCheck + */ + private $phpReadinessCheck; + + /** + * @var \Magento\Framework\Setup\DeclarationInstaller|\PHPUnit_Framework_MockObject_MockObject + */ + private $declarationInstallerMock; + + /** + * @var SchemaListener|\PHPUnit_Framework_MockObject_MockObject + */ + private $schemaListenerMock; + + /** + * Sample DB configuration segment + * @var array + */ + private static $dbConfig = [ + 'default' => [ + ConfigOptionsListConstants::KEY_HOST => '127.0.0.1', + ConfigOptionsListConstants::KEY_NAME => 'magento', + ConfigOptionsListConstants::KEY_USER => 'magento', + ConfigOptionsListConstants::KEY_PASSWORD => '', + ] + ]; - /** - * Instantiates the object with mocks - * - * @param \PHPUnit_Framework_MockObject_MockObject|bool $connectionFactory - * @param \PHPUnit_Framework_MockObject_MockObject|bool $objectManagerProvider - * @return Installer - */ - private function createObject($connectionFactory = false, $objectManagerProvider = false) - { - if (!$connectionFactory) { - $connectionFactory = $this->createMock(\Magento\Setup\Module\ConnectionFactory::class); - $connectionFactory->expects($this->any())->method('create')->willReturn($this->connection); - } - if (!$objectManagerProvider) { - $objectManagerProvider = - $this->createMock(\Magento\Setup\Model\ObjectManagerProvider::class); - $objectManagerProvider->expects($this->any())->method('get')->willReturn($this->objectManager); + /** + * @var \Magento\Framework\Model\ResourceModel\Db\Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var PatchApplier|\PHPUnit_Framework_MockObject_MockObject + */ + private $patchApplierMock; + + /** + * @var PatchApplierFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $patchApplierFactoryMock; + + protected function setUp() + { + $this->filePermissions = $this->createMock(\Magento\Framework\Setup\FilePermissions::class); + $this->configWriter = $this->createMock(\Magento\Framework\App\DeploymentConfig\Writer::class); + $this->configReader = $this->createMock(\Magento\Framework\App\DeploymentConfig\Reader::class); + $this->config = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); + + $this->moduleList = $this->getMockForAbstractClass(\Magento\Framework\Module\ModuleListInterface::class); + $this->moduleList->expects($this->any())->method('getOne')->willReturn( + ['setup_version' => '2.0.0'] + ); + $this->moduleList->expects($this->any())->method('getNames')->willReturn( + ['Foo_One', 'Bar_Two'] + ); + $this->moduleLoader = $this->createMock(\Magento\Framework\Module\ModuleList\Loader::class); + $this->directoryList = + $this->createMock(\Magento\Framework\App\Filesystem\DirectoryList::class); + $this->adminFactory = $this->createMock(\Magento\Setup\Model\AdminAccountFactory::class); + $this->logger = $this->getMockForAbstractClass(\Magento\Framework\Setup\LoggerInterface::class); + $this->random = $this->createMock(\Magento\Framework\Math\Random::class); + $this->connection = $this->getMockForAbstractClass(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $this->maintenanceMode = $this->createMock(\Magento\Framework\App\MaintenanceMode::class); + $this->filesystem = $this->createMock(\Magento\Framework\Filesystem::class); + $this->objectManager = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); + $this->contextMock = + $this->createMock(\Magento\Framework\Model\ResourceModel\Db\Context::class); + $this->configModel = $this->createMock(\Magento\Setup\Model\ConfigModel::class); + $this->cleanupFiles = $this->createMock(\Magento\Framework\App\State\CleanupFiles::class); + $this->dbValidator = $this->createMock(\Magento\Setup\Validator\DbValidator::class); + $this->setupFactory = $this->createMock(\Magento\Setup\Module\SetupFactory::class); + $this->dataSetupFactory = $this->createMock(\Magento\Setup\Module\DataSetupFactory::class); + $this->sampleDataState = $this->createMock(\Magento\Framework\Setup\SampleData\State::class); + $this->componentRegistrar = + $this->createMock(\Magento\Framework\Component\ComponentRegistrar::class); + $this->phpReadinessCheck = $this->createMock(\Magento\Setup\Model\PhpReadinessCheck::class); + $this->declarationInstallerMock = $this->createMock(DeclarationInstaller::class); + $this->schemaListenerMock = $this->createMock(SchemaListener::class); + $this->patchApplierFactoryMock = $this->createMock(PatchApplierFactory::class); + $this->patchApplierMock = $this->createMock(PatchApplier::class); + $this->patchApplierFactoryMock->expects($this->any())->method('create')->willReturn( + $this->patchApplierMock + ); + $this->object = $this->createObject(); } - return new Installer( - $this->filePermissions, - $this->configWriter, - $this->configReader, - $this->config, - $this->moduleList, - $this->moduleLoader, - $this->adminFactory, - $this->logger, - $connectionFactory, - $this->maintenanceMode, - $this->filesystem, - $objectManagerProvider, - $this->contextMock, - $this->configModel, - $this->cleanupFiles, - $this->dbValidator, - $this->setupFactory, - $this->dataSetupFactory, - $this->sampleDataState, - $this->componentRegistrar, - $this->phpReadinessCheck, - $this->declarationInstallerMock - ); - } + /** + * Instantiates the object with mocks + * @param \PHPUnit_Framework_MockObject_MockObject|bool $connectionFactory + * @param \PHPUnit_Framework_MockObject_MockObject|bool $objectManagerProvider + * @return Installer + */ + private function createObject($connectionFactory = false, $objectManagerProvider = false) + { + if (!$connectionFactory) { + $connectionFactory = $this->createMock(\Magento\Setup\Module\ConnectionFactory::class); + $connectionFactory->expects($this->any())->method('create')->willReturn($this->connection); + } + if (!$objectManagerProvider) { + $objectManagerProvider = + $this->createMock(\Magento\Setup\Model\ObjectManagerProvider::class); + $objectManagerProvider->expects($this->any())->method('get')->willReturn($this->objectManager); + } + + return new Installer( + $this->filePermissions, + $this->configWriter, + $this->configReader, + $this->config, + $this->moduleList, + $this->moduleLoader, + $this->adminFactory, + $this->logger, + $connectionFactory, + $this->maintenanceMode, + $this->filesystem, + $objectManagerProvider, + $this->contextMock, + $this->configModel, + $this->cleanupFiles, + $this->dbValidator, + $this->setupFactory, + $this->dataSetupFactory, + $this->sampleDataState, + $this->componentRegistrar, + $this->phpReadinessCheck, + $this->declarationInstallerMock + ); + } - public function testInstall() - { - $request = [ - ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', - ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', - ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', - ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', - ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', - ]; - $this->config->expects($this->atLeastOnce()) - ->method('get') - ->willReturnMap( + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testInstall() + { + $request = [ + ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', + ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', + ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', + ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', + ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', + ]; + $this->config->expects($this->atLeastOnce()) + ->method('get') + ->willReturnMap( + [ + [ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT, null, true], + [ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, null, true], + ['modules/Magento_User', null, '1'] + ] + ); + $allModules = ['Foo_One' => [], 'Bar_Two' => []]; + + $this->declarationInstallerMock->expects($this->once())->method('installSchema'); + $this->moduleLoader->expects($this->any())->method('load')->willReturn($allModules); + $setup = $this->createMock(\Magento\Setup\Module\Setup::class); + $table = $this->createMock(\Magento\Framework\DB\Ddl\Table::class); + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->setMethods(['getSchemaListener', 'newTable']) + ->getMockForAbstractClass(); + $connection->expects($this->any())->method('getSchemaListener')->willReturn($this->schemaListenerMock); + $setup->expects($this->any())->method('getConnection')->willReturn($connection); + $table->expects($this->any())->method('addColumn')->willReturn($table); + $table->expects($this->any())->method('setComment')->willReturn($table); + $table->expects($this->any())->method('addIndex')->willReturn($table); + $connection->expects($this->any())->method('newTable')->willReturn($table); + $resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->contextMock->expects($this->any())->method('getResources')->willReturn($resource); + $resource->expects($this->any())->method('getConnection')->will($this->returnValue($connection)); + $dataSetup = $this->createMock(\Magento\Setup\Module\DataSetup::class); + $dataSetup->expects($this->any())->method('getConnection')->willReturn($connection); + $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); + $cacheManager->expects($this->any())->method('getAvailableTypes')->willReturn(['foo', 'bar']); + $cacheManager->expects($this->once())->method('setEnabled')->willReturn(['foo', 'bar']); + $cacheManager->expects($this->any())->method('clean'); + $appState = $this->getMockBuilder(\Magento\Framework\App\State::class) + ->disableOriginalConstructor() + ->disableArgumentCloning() + ->getMock(); + $appState->expects($this->once()) + ->method('setAreaCode') + ->with(\Magento\Framework\App\Area::AREA_GLOBAL); + $registry = $this->createMock(\Magento\Framework\Registry::class); + $this->setupFactory->expects($this->atLeastOnce())->method('create')->with($resource)->willReturn($setup); + $this->dataSetupFactory->expects($this->atLeastOnce())->method('create')->willReturn($dataSetup); + $this->objectManager->expects($this->any()) + ->method('create') + ->will($this->returnValueMap([ + [\Magento\Framework\App\Cache\Manager::class, [], $cacheManager], + [\Magento\Framework\App\State::class, [], $appState], + [ + PatchApplierFactory::class, + ['objectManager' => $this->objectManager], + $this->patchApplierFactoryMock + ], + ])); + $this->patchApplierMock->expects($this->exactly(2))->method('applySchemaPatch')->willReturnMap( [ - [ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTION_DEFAULT, null, true], - [ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, null, true], - ['modules/Magento_User', null, '1'] + ['Bar_Two'], + ['Foo_One'], ] ); - $allModules = ['Foo_One' => [], 'Bar_Two' => []]; - - $this->declarationInstallerMock->expects($this->once())->method('installSchema'); - $this->moduleLoader->expects($this->any())->method('load')->willReturn($allModules); - $setup = $this->createMock(\Magento\Setup\Module\Setup::class); - $table = $this->createMock(\Magento\Framework\DB\Ddl\Table::class); - $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) - ->setMethods(['getSchemaListener', 'newTable']) - ->getMockForAbstractClass(); - $connection->expects($this->any())->method('getSchemaListener')->willReturn($this->schemaListenerMock); - $setup->expects($this->any())->method('getConnection')->willReturn($connection); - $table->expects($this->any())->method('addColumn')->willReturn($table); - $table->expects($this->any())->method('setComment')->willReturn($table); - $table->expects($this->any())->method('addIndex')->willReturn($table); - $connection->expects($this->any())->method('newTable')->willReturn($table); - $resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); - $this->contextMock->expects($this->any())->method('getResources')->willReturn($resource); - $resource->expects($this->any())->method('getConnection')->will($this->returnValue($connection)); - $dataSetup = $this->createMock(\Magento\Setup\Module\DataSetup::class); - $dataSetup->expects($this->any())->method('getConnection')->willReturn($connection); - $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); - $cacheManager->expects($this->any())->method('getAvailableTypes')->willReturn(['foo', 'bar']); - $cacheManager->expects($this->once())->method('setEnabled')->willReturn(['foo', 'bar']); - $cacheManager->expects($this->any())->method('clean'); - $appState = $this->getMockBuilder(\Magento\Framework\App\State::class) - ->disableOriginalConstructor() - ->disableArgumentCloning() - ->getMock(); - $appState->expects($this->once()) - ->method('setAreaCode') - ->with(\Magento\Framework\App\Area::AREA_GLOBAL); - $this->setupFactory->expects($this->atLeastOnce())->method('create')->with($resource)->willReturn($setup); - $this->dataSetupFactory->expects($this->atLeastOnce())->method('create')->willReturn($dataSetup); - $this->objectManager->expects($this->any()) - ->method('create') - ->will($this->returnValueMap([ - [\Magento\Framework\App\Cache\Manager::class, [], $cacheManager], - [\Magento\Framework\App\State::class, [], $appState], + $this->patchApplierMock->expects($this->exactly(2))->method('applyDataPatch')->willReturnMap( [ - PatchApplierFactory::class, - ['objectManager' => $this->objectManager], - $this->patchApplierFactoryMock - ], - ])); - $this->patchApplierMock->expects($this->exactly(2))->method('applySchemaPatch')->willReturnMap( - [ - ['Bar_Two'], - ['Foo_One'], - ] - ); - $this->patchApplierMock->expects($this->exactly(2))->method('applyDataPatch')->willReturnMap( - [ - ['Bar_Two'], - ['Foo_One'], - ] - ); - $this->objectManager->expects($this->any()) - ->method('get') - ->will($this->returnValueMap([ - [\Magento\Framework\App\State::class, $appState], - [\Magento\Framework\App\Cache\Manager::class, $cacheManager], - [\Magento\Setup\Model\DeclarationInstaller::class, $this->declarationInstallerMock] - ])); - $this->adminFactory->expects($this->once())->method('create')->willReturn( - $this->createMock(\Magento\Setup\Model\AdminAccount::class) - ); - $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); - $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( - ['responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_SUCCESS] - ); - $this->filePermissions->expects($this->any()) - ->method('getMissingWritablePathsForInstallation') - ->willReturn([]); - $this->filePermissions->expects($this->once()) - ->method('getMissingWritableDirectoriesForDbUpgrade') - ->willReturn([]); - $this->setupLoggerExpectsForInstall(); - - $this->object->install($request); - } - - public function testCheckInstallationFilePermissions() - { - $this->filePermissions - ->expects($this->once()) - ->method('getMissingWritablePathsForInstallation') - ->willReturn([]); - $this->object->checkInstallationFilePermissions(); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage Missing write permissions to the following paths: - */ - public function testCheckInstallationFilePermissionsError() - { - $this->filePermissions - ->expects($this->once()) - ->method('getMissingWritablePathsForInstallation') - ->willReturn(['foo', 'bar']); - $this->object->checkInstallationFilePermissions(); - } + ['Bar_Two'], + ['Foo_One'], + ] + ); + $this->objectManager->expects($this->any()) + ->method('get') + ->will($this->returnValueMap([ + [\Magento\Framework\App\State::class, $appState], + [\Magento\Framework\App\Cache\Manager::class, $cacheManager], + [\Magento\Setup\Model\DeclarationInstaller::class, $this->declarationInstallerMock], + [\Magento\Framework\Registry::class, $registry] + ])); + $this->adminFactory->expects($this->once())->method('create')->willReturn( + $this->createMock(\Magento\Setup\Model\AdminAccount::class) + ); + $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); + $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( + ['responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_SUCCESS] + ); + $this->filePermissions->expects($this->any()) + ->method('getMissingWritablePathsForInstallation') + ->willReturn([]); + $this->filePermissions->expects($this->once()) + ->method('getMissingWritableDirectoriesForDbUpgrade') + ->willReturn([]); + $this->setupLoggerExpectsForInstall(); + + $this->object->install($request); + } - public function testCheckExtensions() - { - $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( - ['responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_SUCCESS] - ); - $this->object->checkExtensions(); - } + public function testCheckInstallationFilePermissions() + { + $this->filePermissions + ->expects($this->once()) + ->method('getMissingWritablePathsForInstallation') + ->willReturn([]); + $this->object->checkInstallationFilePermissions(); + } - /** - * @expectedException \Exception - * @expectedExceptionMessage Missing following extensions: 'foo' - */ - public function testCheckExtensionsError() - { - $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( - ['responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_ERROR, - 'data'=>['required'=>['foo', 'bar'], 'missing'=>['foo']]] - ); - $this->object->checkExtensions(); - } + /** + * @expectedException \Exception + * @expectedExceptionMessage Missing write permissions to the following paths: + */ + public function testCheckInstallationFilePermissionsError() + { + $this->filePermissions + ->expects($this->once()) + ->method('getMissingWritablePathsForInstallation') + ->willReturn(['foo', 'bar']); + $this->object->checkInstallationFilePermissions(); + } - public function testCheckApplicationFilePermissions() - { - $this->filePermissions - ->expects($this->once()) - ->method('getUnnecessaryWritableDirectoriesForApplication') - ->willReturn(['foo', 'bar']); - $expectedMessage = "For security, remove write permissions from these directories: 'foo' 'bar'"; - $this->logger->expects($this->once())->method('log')->with($expectedMessage); - $this->object->checkApplicationFilePermissions(); - $this->assertSame(['message' => [$expectedMessage]], $this->object->getInstallInfo()); - } + public function testCheckExtensions() + { + $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( + ['responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_SUCCESS] + ); + $this->object->checkExtensions(); + } - public function testUpdateModulesSequence() - { - $this->cleanupFiles->expects($this->once())->method('clearCodeGeneratedFiles')->will( - $this->returnValue( + /** + * @expectedException \Exception + * @expectedExceptionMessage Missing following extensions: 'foo' + */ + public function testCheckExtensionsError() + { + $this->phpReadinessCheck->expects($this->once())->method('checkPhpExtensions')->willReturn( [ - "The directory '/generation' doesn't exist - skipping cleanup", + 'responseType' => \Magento\Setup\Controller\ResponseTypeInterface::RESPONSE_TYPE_ERROR, + 'data' => ['required' => ['foo', 'bar'], 'missing' => ['foo']] ] - ) - ); - $installer = $this->prepareForUpdateModulesTests(); - - $this->logger->expects($this->at(0))->method('log')->with('Cache cleared successfully'); - $this->logger->expects($this->at(1))->method('log')->with('File system cleanup:'); - $this->logger->expects($this->at(2))->method('log') - ->with('The directory \'/generation\' doesn\'t exist - skipping cleanup'); - $this->logger->expects($this->at(3))->method('log')->with('Updating modules:'); - $installer->updateModulesSequence(false); - } + ); + $this->object->checkExtensions(); + } - public function testUpdateModulesSequenceKeepGenerated() - { - $this->cleanupFiles->expects($this->never())->method('clearCodeGeneratedClasses'); + public function testCheckApplicationFilePermissions() + { + $this->filePermissions + ->expects($this->once()) + ->method('getUnnecessaryWritableDirectoriesForApplication') + ->willReturn(['foo', 'bar']); + $expectedMessage = "For security, remove write permissions from these directories: 'foo' 'bar'"; + $this->logger->expects($this->once())->method('log')->with($expectedMessage); + $this->object->checkApplicationFilePermissions(); + $this->assertSame(['message' => [$expectedMessage]], $this->object->getInstallInfo()); + } - $installer = $this->prepareForUpdateModulesTests(); + public function testUpdateModulesSequence() + { + $this->cleanupFiles->expects($this->once())->method('clearCodeGeneratedFiles')->will( + $this->returnValue( + [ + "The directory '/generation' doesn't exist - skipping cleanup", + ] + ) + ); + $installer = $this->prepareForUpdateModulesTests(); + + $this->logger->expects($this->at(0))->method('log')->with('Cache cleared successfully'); + $this->logger->expects($this->at(1))->method('log')->with('File system cleanup:'); + $this->logger->expects($this->at(2))->method('log') + ->with('The directory \'/generation\' doesn\'t exist - skipping cleanup'); + $this->logger->expects($this->at(3))->method('log')->with('Updating modules:'); + $installer->updateModulesSequence(false); + } - $this->logger->expects($this->at(0))->method('log')->with('Cache cleared successfully'); - $this->logger->expects($this->at(1))->method('log')->with('Updating modules:'); - $installer->updateModulesSequence(true); - } + public function testUpdateModulesSequenceKeepGenerated() + { + $this->cleanupFiles->expects($this->never())->method('clearCodeGeneratedClasses'); - public function testUninstall() - { - $this->config->expects($this->once()) - ->method('get') - ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS) - ->willReturn([]); - $this->configReader->expects($this->once())->method('getFiles')->willReturn(['ConfigOne.php', 'ConfigTwo.php']); - $configDir = $this->getMockForAbstractClass( - \Magento\Framework\Filesystem\Directory\WriteInterface::class - ); - $configDir - ->expects($this->exactly(2)) - ->method('getAbsolutePath') - ->will( - $this->returnValueMap( + $installer = $this->prepareForUpdateModulesTests(); + + $this->logger->expects($this->at(0))->method('log')->with('Cache cleared successfully'); + $this->logger->expects($this->at(1))->method('log')->with('Updating modules:'); + $installer->updateModulesSequence(true); + } + + public function testUninstall() + { + $this->config->expects($this->once()) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS) + ->willReturn([]); + $this->configReader->expects($this->once())->method('getFiles')->willReturn([ + 'ConfigOne.php', + 'ConfigTwo.php' + ]); + $configDir = $this->getMockForAbstractClass( + \Magento\Framework\Filesystem\Directory\WriteInterface::class + ); + $configDir + ->expects($this->exactly(2)) + ->method('getAbsolutePath') + ->will( + $this->returnValueMap( + [ + ['ConfigOne.php', '/config/ConfigOne.php'], + ['ConfigTwo.php', '/config/ConfigTwo.php'] + ] + ) + ); + $this->filesystem + ->expects($this->any()) + ->method('getDirectoryWrite') + ->will($this->returnValueMap([ + [DirectoryList::CONFIG, DriverPool::FILE, $configDir], + ])); + $this->logger->expects($this->at(0))->method('log')->with('Starting Magento uninstallation:'); + $this->logger + ->expects($this->at(2)) + ->method('log') + ->with('No database connection defined - skipping database cleanup'); + $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); + $cacheManager->expects($this->once())->method('getAvailableTypes')->willReturn(['foo', 'bar']); + $cacheManager->expects($this->once())->method('clean'); + $this->objectManager->expects($this->any()) + ->method('get') + ->with(\Magento\Framework\App\Cache\Manager::class) + ->willReturn($cacheManager); + $this->logger->expects($this->at(1))->method('log')->with('Cache cleared successfully'); + $this->logger->expects($this->at(3))->method('log')->with('File system cleanup:'); + $this->logger + ->expects($this->at(4)) + ->method('log') + ->with("The directory '/var' doesn't exist - skipping cleanup"); + $this->logger + ->expects($this->at(5)) + ->method('log') + ->with("The directory '/static' doesn't exist - skipping cleanup"); + $this->logger + ->expects($this->at(6)) + ->method('log') + ->with("The file '/config/ConfigOne.php' doesn't exist - skipping cleanup"); + $this->logger + ->expects($this->at(7)) + ->method('log') + ->with("The file '/config/ConfigTwo.php' doesn't exist - skipping cleanup"); + $this->logger->expects($this->once())->method('logSuccess')->with('Magento uninstallation complete.'); + $this->cleanupFiles->expects($this->once())->method('clearAllFiles')->will( + $this->returnValue( [ - ['ConfigOne.php', '/config/ConfigOne.php'], - ['ConfigTwo.php', '/config/ConfigTwo.php'] + "The directory '/var' doesn't exist - skipping cleanup", + "The directory '/static' doesn't exist - skipping cleanup" ] ) ); - $this->filesystem - ->expects($this->any()) - ->method('getDirectoryWrite') - ->will($this->returnValueMap([ - [DirectoryList::CONFIG, DriverPool::FILE, $configDir], - ])); - $this->logger->expects($this->at(0))->method('log')->with('Starting Magento uninstallation:'); - $this->logger - ->expects($this->at(2)) - ->method('log') - ->with('No database connection defined - skipping database cleanup'); - $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); - $cacheManager->expects($this->once())->method('getAvailableTypes')->willReturn(['foo', 'bar']); - $cacheManager->expects($this->once())->method('clean'); - $this->objectManager->expects($this->any()) - ->method('get') - ->with(\Magento\Framework\App\Cache\Manager::class) - ->willReturn($cacheManager); - $this->logger->expects($this->at(1))->method('log')->with('Cache cleared successfully'); - $this->logger->expects($this->at(3))->method('log')->with('File system cleanup:'); - $this->logger - ->expects($this->at(4)) - ->method('log') - ->with("The directory '/var' doesn't exist - skipping cleanup"); - $this->logger - ->expects($this->at(5)) - ->method('log') - ->with("The directory '/static' doesn't exist - skipping cleanup"); - $this->logger - ->expects($this->at(6)) - ->method('log') - ->with("The file '/config/ConfigOne.php' doesn't exist - skipping cleanup"); - $this->logger - ->expects($this->at(7)) - ->method('log') - ->with("The file '/config/ConfigTwo.php' doesn't exist - skipping cleanup"); - $this->logger->expects($this->once())->method('logSuccess')->with('Magento uninstallation complete.'); - $this->cleanupFiles->expects($this->once())->method('clearAllFiles')->will( - $this->returnValue( - [ - "The directory '/var' doesn't exist - skipping cleanup", - "The directory '/static' doesn't exist - skipping cleanup" - ] - ) - ); - $this->object->uninstall(); - } - - public function testCleanupDb() - { - $this->config->expects($this->once()) - ->method('get') - ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS) - ->willReturn(self::$dbConfig); - $this->connection->expects($this->at(0))->method('quoteIdentifier')->with('magento')->willReturn('`magento`'); - $this->connection->expects($this->at(1))->method('query')->with('DROP DATABASE IF EXISTS `magento`'); - $this->connection->expects($this->at(2))->method('query')->with('CREATE DATABASE IF NOT EXISTS `magento`'); - $this->logger->expects($this->once())->method('log')->with('Cleaning up database `magento`'); - $this->object->cleanupDb(); - } + $this->object->uninstall(); + } - /** - * Prepare mocks for update modules tests and returns the installer to use - * - * @return Installer - */ - private function prepareForUpdateModulesTests() - { - $allModules = [ - 'Foo_One' => [], - 'Bar_Two' => [], - 'New_Module' => [], - ]; + public function testCleanupDb() + { + $this->config->expects($this->once()) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS) + ->willReturn(self::$dbConfig); + $this->connection->expects($this->at(0))->method('quoteIdentifier')->with('magento')->willReturn( + '`magento`' + ); + $this->connection->expects($this->at(1))->method('query')->with('DROP DATABASE IF EXISTS `magento`'); + $this->connection->expects($this->at(2))->method('query')->with('CREATE DATABASE IF NOT EXISTS `magento`'); + $this->logger->expects($this->once())->method('log')->with('Cleaning up database `magento`'); + $this->object->cleanupDb(); + } - $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); - $cacheManager->expects($this->once())->method('getAvailableTypes')->willReturn(['foo', 'bar']); - $cacheManager->expects($this->once())->method('clean'); - $this->objectManager->expects($this->any()) - ->method('get') - ->will($this->returnValueMap([ - [\Magento\Framework\App\Cache\Manager::class, $cacheManager] - ])); - $this->moduleLoader->expects($this->once())->method('load')->willReturn($allModules); - - $expectedModules = [ - ConfigFilePool::APP_CONFIG => [ - 'modules' => [ - 'Bar_Two' => 0, - 'Foo_One' => 1, - 'New_Module' => 1 + /** + * Prepare mocks for update modules tests and returns the installer to use + * @return Installer + */ + private function prepareForUpdateModulesTests() + { + $allModules = [ + 'Foo_One' => [], + 'Bar_Two' => [], + 'New_Module' => [], + ]; + + $cacheManager = $this->createMock(\Magento\Framework\App\Cache\Manager::class); + $cacheManager->expects($this->once())->method('getAvailableTypes')->willReturn(['foo', 'bar']); + $cacheManager->expects($this->once())->method('clean'); + $this->objectManager->expects($this->any()) + ->method('get') + ->will($this->returnValueMap([ + [\Magento\Framework\App\Cache\Manager::class, $cacheManager] + ])); + $this->moduleLoader->expects($this->once())->method('load')->willReturn($allModules); + + $expectedModules = [ + ConfigFilePool::APP_CONFIG => [ + 'modules' => [ + 'Bar_Two' => 0, + 'Foo_One' => 1, + 'New_Module' => 1 + ] ] - ] - ]; + ]; + + $this->config->expects($this->atLeastOnce()) + ->method('get') + ->with(ConfigOptionsListConstants::KEY_MODULES) + ->willReturn(true); - $this->config->expects($this->atLeastOnce()) - ->method('get') - ->with(ConfigOptionsListConstants::KEY_MODULES) - ->willReturn(true); + $newObject = $this->createObject(false, false); + $this->configReader->expects($this->once())->method('load') + ->willReturn(['modules' => ['Bar_Two' => 0, 'Foo_One' => 1, 'Old_Module' => 0]]); + $this->configWriter->expects($this->once())->method('saveConfig')->with($expectedModules); - $newObject = $this->createObject(false, false); - $this->configReader->expects($this->once())->method('load') - ->willReturn(['modules' => ['Bar_Two' => 0, 'Foo_One' => 1, 'Old_Module' => 0] ]); - $this->configWriter->expects($this->once())->method('saveConfig')->with($expectedModules); + return $newObject; + } - return $newObject; + /** + * Set up logger expectations for install method + * @return void + */ + private function setupLoggerExpectsForInstall() + { + $this->logger->expects($this->at(0))->method('log')->with('Starting Magento installation:'); + $this->logger->expects($this->at(1))->method('log')->with('File permissions check...'); + $this->logger->expects($this->at(3))->method('log')->with('Required extensions check...'); + // at(2) invokes logMeta() + $this->logger->expects($this->at(5))->method('log')->with('Enabling Maintenance Mode...'); + // at(4) - logMeta and so on... + $this->logger->expects($this->at(7))->method('log')->with('Installing deployment configuration...'); + $this->logger->expects($this->at(9))->method('log')->with('Installing database schema:'); + $this->logger->expects($this->at(11))->method('log')->with("Module 'Foo_One':"); + $this->logger->expects($this->at(13))->method('log')->with("Module 'Bar_Two':"); + $this->logger->expects($this->at(15))->method('log')->with('Schema post-updates:'); + $this->logger->expects($this->at(16))->method('log')->with("Module 'Foo_One':"); + $this->logger->expects($this->at(18))->method('log')->with("Module 'Bar_Two':"); + $this->logger->expects($this->at(21))->method('log')->with('Installing user configuration...'); + $this->logger->expects($this->at(23))->method('log')->with('Enabling caches:'); + $this->logger->expects($this->at(27))->method('log')->with('Installing data...'); + $this->logger->expects($this->at(28))->method('log')->with('Data install/update:'); + $this->logger->expects($this->at(29))->method('log')->with("Module 'Foo_One':"); + $this->logger->expects($this->at(31))->method('log')->with("Module 'Bar_Two':"); + $this->logger->expects($this->at(33))->method('log')->with('Data post-updates:'); + $this->logger->expects($this->at(34))->method('log')->with("Module 'Foo_One':"); + $this->logger->expects($this->at(36))->method('log')->with("Module 'Bar_Two':"); + $this->logger->expects($this->at(39))->method('log')->with('Installing admin user...'); + $this->logger->expects($this->at(41))->method('log')->with('Caches clearing:'); + $this->logger->expects($this->at(44))->method('log')->with('Disabling Maintenance Mode:'); + $this->logger->expects($this->at(46))->method('log')->with('Post installation file permissions check...'); + $this->logger->expects($this->at(48))->method('log')->with('Write installation date...'); + $this->logger->expects($this->at(50))->method('logSuccess')->with('Magento installation complete.'); + $this->logger->expects($this->at(52))->method('log') + ->with('Sample Data is installed with errors. See log file for details'); + } } +} + +namespace Magento\Setup\Model { /** - * Set up logger expectations for install method + * Mocking autoload function * - * @return void + * @returns array */ - private function setupLoggerExpectsForInstall() + function spl_autoload_functions() { - $this->logger->expects($this->at(0))->method('log')->with('Starting Magento installation:'); - $this->logger->expects($this->at(1))->method('log')->with('File permissions check...'); - $this->logger->expects($this->at(3))->method('log')->with('Required extensions check...'); - // at(2) invokes logMeta() - $this->logger->expects($this->at(5))->method('log')->with('Enabling Maintenance Mode...'); - // at(4) - logMeta and so on... - $this->logger->expects($this->at(7))->method('log')->with('Installing deployment configuration...'); - $this->logger->expects($this->at(9))->method('log')->with('Installing database schema:'); - $this->logger->expects($this->at(11))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(13))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(15))->method('log')->with('Schema post-updates:'); - $this->logger->expects($this->at(16))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(18))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(21))->method('log')->with('Installing user configuration...'); - $this->logger->expects($this->at(23))->method('log')->with('Enabling caches:'); - $this->logger->expects($this->at(27))->method('log')->with('Installing data...'); - $this->logger->expects($this->at(28))->method('log')->with('Data install/update:'); - $this->logger->expects($this->at(29))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(31))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(33))->method('log')->with('Data post-updates:'); - $this->logger->expects($this->at(34))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(36))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(39))->method('log')->with('Installing admin user...'); - $this->logger->expects($this->at(41))->method('log')->with('Caches clearing:'); - $this->logger->expects($this->at(44))->method('log')->with('Disabling Maintenance Mode:'); - $this->logger->expects($this->at(46))->method('log')->with('Post installation file permissions check...'); - $this->logger->expects($this->at(48))->method('log')->with('Write installation date...'); - $this->logger->expects($this->at(50))->method('logSuccess')->with('Magento installation complete.'); - $this->logger->expects($this->at(52))->method('log') - ->with('Sample Data is installed with errors. See log file for details'); + return ['mock_function_one', 'mock_function_two']; } } - -namespace Magento\Setup\Model; - -/** - * Mocking autoload function - * - * @returns array - */ -function spl_autoload_functions() -{ - return ['mock_function_one', 'mock_function_two']; -} diff --git a/setup/view/layout/layout.phtml b/setup/view/layout/layout.phtml index 613ff391a8fff..0415aff91fc7b 100644 --- a/setup/view/layout/layout.phtml +++ b/setup/view/layout/layout.phtml @@ -5,8 +5,6 @@ */ ?> <?= $this->doctype() ?> -<!--[if IE 9]> -<html class="ie9" lang="en" ng-app="magentoSetup"><![endif]--> <!--[if !IE]><!--> <html lang="en" ng-app="magentoSetup"> <!--<![endif]--> diff --git a/setup/view/magento/setup/navigation/side-menu.phtml b/setup/view/magento/setup/navigation/side-menu.phtml index 58d12a4de448a..6354af875ae8c 100644 --- a/setup/view/magento/setup/navigation/side-menu.phtml +++ b/setup/view/magento/setup/navigation/side-menu.phtml @@ -6,6 +6,13 @@ // @codingStandardsIgnoreFile +use Magento\Backend\Model\UrlInterface; +use Magento\Framework\App\ObjectManager; + +$objectManager = ObjectManager::getInstance(); +/** @var Magento\Backend\Model\UrlInterface $backendUrl */ +$backendUrl = $objectManager->get(UrlInterface::class); + ?> <?php $expressions = []; foreach ( $this->main as $item ): ?> <?php $expressions[] = '!$state.is(\'' . $item['id'] . '\')'; @@ -20,13 +27,13 @@ ng-show="<?= implode( '&&', $expressions) ?>" > <nav class="admin__menu" ng-controller="mainController"> - <span - class="logo logo-static" + <a href="<?= $backendUrl->getBaseUrl() . $backendUrl->getAreaFrontName(); ?>" + class="logo" data-edition="Community Edition"> <img class="logo-img" src="./pub/images/logo.svg" alt="Magento Admin Panel"> - </span> + </a> <ul id="nav" role="menubar"> <li class="item-home level-0" ng-class="{_active: $state.current.name === 'root.home'}"> <a href="" ui-sref="root.home">